From fef6e6a583e00ddaa438d2c115ad90964d8a9e8e Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Tue, 7 May 2024 19:21:25 +0200 Subject: [PATCH 001/104] fix(iast): unexpected Import error catching (#9188) When loading a Python module, AST patching was catching the ImportError exception and not propagating it correctly, causing if one wanted to catch this exception, it couldn't be done, generating unexpected effects as in the BeautifulSoup4 package. In Example: ```python try: from . import _html5lib register_treebuilders_from(_html5lib) except ImportError: # They don't have html5lib installed. pass ``` ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. - [x] If change touches code that signs or publishes builds or packages, or handles credentials of any kind, I've requested a review from `@DataDog/security-design-and-guidance`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Juanjo Alvarez Martinez Co-authored-by: Federico Mon --- ddtrace/appsec/_iast/_loader.py | 5 +--- ...ix-iast-import-error-2eedea126bb9d92d.yaml | 4 +++ tests/appsec/app.py | 8 ++++++ .../packages/pkg_beautifulsoup4.py | 26 +++++++++++++++++++ tests/appsec/iast_packages/test_packages.py | 1 + .../integrations/module_with_import_errors.py | 11 ++++++++ .../integrations/test_flask_iast_patching.py | 20 ++++++++++++++ 7 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/iast-fix-iast-import-error-2eedea126bb9d92d.yaml create mode 100644 tests/appsec/iast_packages/packages/pkg_beautifulsoup4.py create mode 100644 tests/appsec/integrations/module_with_import_errors.py create mode 100644 tests/appsec/integrations/test_flask_iast_patching.py diff --git a/ddtrace/appsec/_iast/_loader.py b/ddtrace/appsec/_iast/_loader.py index 41ac26dfda3..f211191430b 100644 --- a/ddtrace/appsec/_iast/_loader.py +++ b/ddtrace/appsec/_iast/_loader.py @@ -32,10 +32,7 @@ def _exec_iast_patched_module(module_watchdog, module): if compiled_code: # Patched source is executed instead of original module - try: - exec(compiled_code, module.__dict__) # nosec B102 - except ImportError: - log.debug("Unexpected exception while executing patched code", exc_info=True) + exec(compiled_code, module.__dict__) # nosec B102 elif module_watchdog.loader is not None: try: module_watchdog.loader.exec_module(module) diff --git a/releasenotes/notes/iast-fix-iast-import-error-2eedea126bb9d92d.yaml b/releasenotes/notes/iast-fix-iast-import-error-2eedea126bb9d92d.yaml new file mode 100644 index 00000000000..a7f8d0172a0 --- /dev/null +++ b/releasenotes/notes/iast-fix-iast-import-error-2eedea126bb9d92d.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + Code Security: This fixes a bug in the AST patching process where ``ImportError`` exceptions were being caught, interfering with the proper application cycle if an ``ImportError`` was expected." diff --git a/tests/appsec/app.py b/tests/appsec/app.py index 7c789280088..f761923c27a 100644 --- a/tests/appsec/app.py +++ b/tests/appsec/app.py @@ -9,6 +9,7 @@ import ddtrace.auto # noqa: F401 # isort: skip +from tests.appsec.iast_packages.packages.pkg_beautifulsoup4 import pkg_beautifulsoup4 from tests.appsec.iast_packages.packages.pkg_chartset_normalizer import pkg_chartset_normalizer from tests.appsec.iast_packages.packages.pkg_google_api_core import pkg_google_api_core from tests.appsec.iast_packages.packages.pkg_idna import pkg_idna @@ -17,9 +18,11 @@ from tests.appsec.iast_packages.packages.pkg_pyyaml import pkg_pyyaml from tests.appsec.iast_packages.packages.pkg_requests import pkg_requests from tests.appsec.iast_packages.packages.pkg_urllib3 import pkg_urllib3 +import tests.appsec.integrations.module_with_import_errors as module_with_import_errors app = Flask(__name__) +app.register_blueprint(pkg_beautifulsoup4) app.register_blueprint(pkg_chartset_normalizer) app.register_blueprint(pkg_google_api_core) app.register_blueprint(pkg_idna) @@ -59,5 +62,10 @@ def iast_cmdi_vulnerability(): return resp +@app.route("/iast-ast-patching-import-error", methods=["GET"]) +def iast_ast_patching_import_error(): + return Response(str(module_with_import_errors.verbal_kint_is_keyser_soze)) + + if __name__ == "__main__": app.run(debug=False, port=8000) diff --git a/tests/appsec/iast_packages/packages/pkg_beautifulsoup4.py b/tests/appsec/iast_packages/packages/pkg_beautifulsoup4.py new file mode 100644 index 00000000000..6f55b82ba92 --- /dev/null +++ b/tests/appsec/iast_packages/packages/pkg_beautifulsoup4.py @@ -0,0 +1,26 @@ +""" +beautifulsoup4==4.12.3 + +https://pypi.org/project/beautifulsoup4/ +""" +from flask import Blueprint +from flask import request + +from .utils import ResultResponse + + +pkg_beautifulsoup4 = Blueprint("package_beautifulsoup4", __name__) + + +@pkg_beautifulsoup4.route("/beautifulsoup4") +def pkg_beautifusoup4_view(): + from bs4 import BeautifulSoup + + response = ResultResponse(request.args.get("package_param")) + + try: + html = response.package_param + "".join(BeautifulSoup(html, features="lxml").findAll(string=True)).lstrip() + except Exception: + pass + return response.json() diff --git a/tests/appsec/iast_packages/test_packages.py b/tests/appsec/iast_packages/test_packages.py index 7cfe86ce087..5d8f9c1ac98 100644 --- a/tests/appsec/iast_packages/test_packages.py +++ b/tests/appsec/iast_packages/test_packages.py @@ -87,6 +87,7 @@ def install(self): ["https", None, "www.datadoghq.com", None, "/", None, None], "www.datadoghq.com", ), + PackageForTesting("beautifulsoup4", "4.12.3", "", "", ""), ] diff --git a/tests/appsec/integrations/module_with_import_errors.py b/tests/appsec/integrations/module_with_import_errors.py new file mode 100644 index 00000000000..aa65299d081 --- /dev/null +++ b/tests/appsec/integrations/module_with_import_errors.py @@ -0,0 +1,11 @@ +try: + from . import _keyser_soze # noqa: F401 + + verbal_kint_is_keyser_soze = True +except ImportError: + verbal_kint_is_keyser_soze = False + + +def func(): + VARIABLE_TO_FORCE_AST_PATCHINT = "a" + "b" + return VARIABLE_TO_FORCE_AST_PATCHINT diff --git a/tests/appsec/integrations/test_flask_iast_patching.py b/tests/appsec/integrations/test_flask_iast_patching.py new file mode 100644 index 00000000000..522dede932e --- /dev/null +++ b/tests/appsec/integrations/test_flask_iast_patching.py @@ -0,0 +1,20 @@ +from tests.appsec.appsec_utils import flask_server + + +def test_flask_iast_ast_patching_import_error(): + """this is a regression test for a bug with IAST + BeautifulSoup4, we were catching the ImportError exception in + ddtrace.appsec._iast._loader + try: + from . import _html5lib + register_treebuilders_from(_html5lib) + except ImportError: + # They don't have html5lib installed. + pass + """ + with flask_server(iast_enabled="true", token=None) as context: + _, flask_client, pid = context + + response = flask_client.get("/iast-ast-patching-import-error") + + assert response.status_code == 200 + assert response.content == b"False" From e5cddcdc6f045b7302f7d4ee237a4e101f91ad85 Mon Sep 17 00:00:00 2001 From: David Sanchez <838104+sanchda@users.noreply.github.com> Date: Tue, 7 May 2024 12:32:46 -0500 Subject: [PATCH 002/104] chore(codeowners): add new directory to profiling (#9175) Probably should have done this a while ago. This directory is the new home for certain native-code assets for supporting the libdatadog (profiling) exporter and "stack v2." ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [ ] Title is accurate - [ ] All changes are related to the pull request's stated goal - [ ] Description motivates each change - [ ] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [ ] Testing strategy adequately addresses listed risks - [ ] Change is maintainable (easy to change, telemetry, documentation) - [ ] Release note makes sense to a user of the library - [ ] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [ ] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Co-authored-by: sanchda --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 01e3effa28e..485214bbe12 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -76,6 +76,7 @@ tests/contrib/*/test*appsec*.py @DataDog/asm-python # Profiling ddtrace/profiling @DataDog/profiling-python @DataDog/apm-core-python +ddtrace/internal/datadog/profiling @DataDog/profiling-python @DataDog/apm-core-python tests/profiling @DataDog/profiling-python @DataDog/apm-core-python # MLObs From 666d1744a112b208d79389b0117f79c1332b6f05 Mon Sep 17 00:00:00 2001 From: Emmett Butler <723615+emmettbutler@users.noreply.github.com> Date: Tue, 7 May 2024 10:56:16 -0700 Subject: [PATCH 003/104] ci(profiling): adjust expectation (#9191) This change updates a line-number expectation in the profiling test suite to match reality. ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- tests/profiling/collector/test_threading.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/profiling/collector/test_threading.py b/tests/profiling/collector/test_threading.py index 2cd538a5a49..5b2e7b92d6d 100644 --- a/tests/profiling/collector/test_threading.py +++ b/tests/profiling/collector/test_threading.py @@ -254,7 +254,12 @@ def play_with_lock(): # It's called through pytest so I'm sure it's gonna be that long, right? assert len(event.frames) > 3 assert event.nframes > 3 - assert event.frames[0] == ("tests/profiling/collector/test_threading.py", 238, "play_with_lock", "") + assert event.frames[0] == ( + "tests/profiling/collector/test_threading.py", + 238, + "play_with_lock", + "", + ), event.frames assert event.sampling_pct == 100 break else: @@ -268,7 +273,12 @@ def play_with_lock(): # It's called through pytest so I'm sure it's gonna be that long, right? assert len(event.frames) > 3 assert event.nframes > 3 - assert event.frames[0] == ("tests/profiling/collector/test_threading.py", 238, "play_with_lock", "") + assert event.frames[0] == ( + "tests/profiling/collector/test_threading.py", + 239, + "play_with_lock", + "", + ), event.frames assert event.sampling_pct == 100 break else: From c3085672583a0008a4f21d5a04b9e6b0f9e59265 Mon Sep 17 00:00:00 2001 From: "Tahir H. Butt" Date: Tue, 7 May 2024 14:05:29 -0400 Subject: [PATCH 004/104] docs: add falcon version support (#8778) Adds falcon version support to integration table in documentation. ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. - [x] If change touches code that signs or publishes builds or packages, or handles credentials of any kind, I've requested a review from `@DataDog/security-design-and-guidance`. ## Reviewer Checklist - [ ] Title is accurate - [ ] All changes are related to the pull request's stated goal - [ ] Description motivates each change - [ ] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [ ] Testing strategy adequately addresses listed risks - [ ] Change is maintainable (easy to change, telemetry, documentation) - [ ] Release note makes sense to a user of the library - [ ] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [ ] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- docs/index.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/index.rst b/docs/index.rst index 582103f7d3d..e218f9d6b75 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -82,6 +82,8 @@ contacting support. +--------------------------------------------------+---------------+----------------+ | :ref:`elasticsearch` | >= 1.10,< 8.0 | Yes | +--------------------------------------------------+---------------+----------------+ +| :ref:`falcon` | >= 3.0 | Yes | ++--------------------------------------------------+---------------+----------------+ | :ref:`fastapi` | >= 0.64 | Yes | +--------------------------------------------------+---------------+----------------+ | :ref:`flask` | >= 1.0 | Yes | From 3a12bacf64563ce94cf2f7797b2b8e12c6d5524c Mon Sep 17 00:00:00 2001 From: Emmett Butler <723615+emmettbutler@users.noreply.github.com> Date: Tue, 7 May 2024 12:03:33 -0700 Subject: [PATCH 005/104] chore(telemetry): remove _trace_enabled config option (#9164) This change removes the redundant `_trace_enabled` configuration option from `settings.config`. It was only used in the instrumentation telemetry writer and its functionality is fully duplicated by the `_tracing_enabled` option. Existing tests cover this change. ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/internal/telemetry/writer.py | 8 ++------ ddtrace/settings/config.py | 5 ----- tests/integration/test_settings.py | 8 ++++---- tests/telemetry/test_writer.py | 4 +--- 4 files changed, 7 insertions(+), 18 deletions(-) diff --git a/ddtrace/internal/telemetry/writer.py b/ddtrace/internal/telemetry/writer.py index 8c9ebf6f1f7..ba6fed2cf17 100644 --- a/ddtrace/internal/telemetry/writer.py +++ b/ddtrace/internal/telemetry/writer.py @@ -370,10 +370,7 @@ def add_configs_changed(self, cfg_names): def _telemetry_entry(self, cfg_name: str) -> Tuple[str, str, _ConfigSource]: item = config._config[cfg_name] - if cfg_name == "_trace_enabled": - name = "trace_enabled" - value = "true" if item.value() else "false" - elif cfg_name == "_profiling_enabled": + if cfg_name == "_profiling_enabled": name = "profiling_enabled" value = "true" if item.value() else "false" elif cfg_name == "_asm_enabled": @@ -398,7 +395,7 @@ def _telemetry_entry(self, cfg_name: str) -> Tuple[str, str, _ConfigSource]: name = "trace_tags" value = ",".join(":".join(x) for x in item.value().items()) elif cfg_name == "_tracing_enabled": - name = "tracing_enabled" + name = "trace_enabled" value = "true" if item.value() else "false" elif cfg_name == "_sca_enabled": name = "DD_APPSEC_SCA_ENABLED" @@ -432,7 +429,6 @@ def _app_started_event(self, register_app_shutdown=True): self.add_configurations( [ - self._telemetry_entry("_trace_enabled"), self._telemetry_entry("_profiling_enabled"), self._telemetry_entry("_asm_enabled"), self._telemetry_entry("_sca_enabled"), diff --git a/ddtrace/settings/config.py b/ddtrace/settings/config.py index fc0083d6222..972d2fff700 100644 --- a/ddtrace/settings/config.py +++ b/ddtrace/settings/config.py @@ -273,11 +273,6 @@ def _parse_global_tags(s): def _default_config(): # type: () -> Dict[str, _ConfigItem] return { - "_trace_enabled": _ConfigItem( - name="trace_enabled", - default=True, - envs=[("DD_TRACE_ENABLED", asbool)], - ), "_trace_sample_rate": _ConfigItem( name="trace_sample_rate", default=1.0, diff --git a/tests/integration/test_settings.py b/tests/integration/test_settings.py index d440ba5f058..2b439a8cf84 100644 --- a/tests/integration/test_settings.py +++ b/tests/integration/test_settings.py @@ -59,8 +59,8 @@ def test_setting_origin_environment(test_agent_session, run_python_code_in_subpr events_trace_tags = _get_telemetry_config_items(events, "trace_tags") assert {"name": "trace_tags", "value": "team:apm,component:web", "origin": "env_var"} in events_trace_tags - events_tracing_enabled = _get_telemetry_config_items(events, "tracing_enabled") - assert {"name": "tracing_enabled", "value": "true", "origin": "env_var"} in events_tracing_enabled + events_tracing_enabled = _get_telemetry_config_items(events, "trace_enabled") + assert {"name": "trace_enabled", "value": "true", "origin": "env_var"} in events_tracing_enabled @pytest.mark.skipif(AGENT_VERSION != "testagent", reason="Tests only compatible with a testagent") @@ -122,9 +122,9 @@ def test_setting_origin_code(test_agent_session, run_python_code_in_subprocess): "origin": "code", } in events_trace_tags - events_tracing_enabled = _get_telemetry_config_items(events, "tracing_enabled") + events_tracing_enabled = _get_telemetry_config_items(events, "trace_enabled") assert { - "name": "tracing_enabled", + "name": "trace_enabled", "value": "false", "origin": "code", } in events_tracing_enabled diff --git a/tests/telemetry/test_writer.py b/tests/telemetry/test_writer.py index c25482e849e..541e002d299 100644 --- a/tests/telemetry/test_writer.py +++ b/tests/telemetry/test_writer.py @@ -136,7 +136,6 @@ def test_app_started_event(telemetry_writer, test_agent_session, mock_time): {"name": "DD_TRACE_WRITER_REUSE_CONNECTIONS", "origin": "unknown", "value": False}, {"name": "ddtrace_auto_used", "origin": "unknown", "value": False}, {"name": "ddtrace_bootstrapped", "origin": "unknown", "value": False}, - {"name": "trace_enabled", "origin": "default", "value": "true"}, {"name": "profiling_enabled", "origin": "default", "value": "false"}, {"name": "data_streams_enabled", "origin": "default", "value": "false"}, {"name": "appsec_enabled", "origin": "default", "value": "false"}, @@ -145,7 +144,7 @@ def test_app_started_event(telemetry_writer, test_agent_session, mock_time): {"name": "trace_header_tags", "origin": "default", "value": ""}, {"name": "logs_injection_enabled", "origin": "default", "value": "false"}, {"name": "trace_tags", "origin": "default", "value": ""}, - {"name": "tracing_enabled", "origin": "default", "value": "true"}, + {"name": "trace_enabled", "origin": "default", "value": "true"}, {"name": "instrumentation_config_id", "origin": "default", "value": ""}, ], key=lambda x: x["name"], @@ -315,7 +314,6 @@ def test_app_started_event_configuration_override( {"name": "logs_injection_enabled", "origin": "env_var", "value": "true"}, {"name": "trace_header_tags", "origin": "default", "value": ""}, {"name": "trace_tags", "origin": "env_var", "value": "team:apm,component:web"}, - {"name": "tracing_enabled", "origin": "env_var", "value": "false"}, {"name": "instrumentation_config_id", "origin": "env_var", "value": "abcedf123"}, ], key=lambda x: x["name"], From dac8aa29c737baaac8b1431b26c7fba487ba4fb8 Mon Sep 17 00:00:00 2001 From: Emmett Butler <723615+emmettbutler@users.noreply.github.com> Date: Tue, 7 May 2024 12:31:58 -0700 Subject: [PATCH 006/104] chore: shorten time to close stale issues and PRs (#9193) This change updates the github actions config to close pull requests and issues that have not been updated in the last 30 days. ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Co-authored-by: Brett Langdon --- .github/workflows/stale.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 59c1f9d6ab1..9a9a7f672a8 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -1,4 +1,4 @@ -name: 'Label stale issues and PRs' +name: 'Close stale issues and PRs' on: schedule: # 00:00:000 UTC @@ -17,16 +17,16 @@ jobs: # DEV: GitHub Actions have an API rate limit of 1000 operations per hour per repository # This limit is shared across all actions operations-per-run: 200 - days-before-close: 180 + days-before-close: 30 exempt-issue-labels: 'proposal' exempt-pr-labels: 'proposal' close-issue-message: | - This issue has been automatically closed after six months of inactivity. If it's a + This issue has been automatically closed after a period of inactivity. If it's a feature request, it has been added to the maintainers' internal backlog and will be included in an upcoming round of feature prioritization. Please comment or reopen if you think this issue was closed in error. close-pr-message: | - This pull request has been automatically closed after six months of inactivity. + This pull request has been automatically closed after a period of inactivity. After this much time, it will likely be easier to open a new pull request with the same changes than to update this one from the base branch. Please comment or reopen if you think this pull request was closed in error. From 22302147a2ac3baa03727c73085fd6575fba60e9 Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Tue, 7 May 2024 21:58:41 -0400 Subject: [PATCH 007/104] chore(distributed_tracing): ensure last datadog parent id tag is always set (#9174) Implements W3C Phase 3. The last datadog parent id is always propagated in a distributed trace (if ddtrace propagation style includes tracecontext). This change will reduce instances where flamegraphs contain missing spans. Note - This change does not require a release note. It is a follow up to a previous PR. Follow up to: https://github.com/DataDog/dd-trace-py/commit/90a3e3fed47763abf64cb4c04c6a32f5c64076cb#diff-18af2a7682cf3014ab4ae236fc54d2454bbf5293e81e098b1d22d39bdb61012d ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/internal/constants.py | 1 + ddtrace/propagation/http.py | 14 +++++++++----- tests/tracer/test_propagation.py | 17 +++++++++++------ 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/ddtrace/internal/constants.py b/ddtrace/internal/constants.py index 50b8e1280e4..7988687a74c 100644 --- a/ddtrace/internal/constants.py +++ b/ddtrace/internal/constants.py @@ -25,6 +25,7 @@ DEFAULT_SAMPLING_RATE_LIMIT = 100 SAMPLING_DECISION_TRACE_TAG_KEY = "_dd.p.dm" LAST_DD_PARENT_ID_KEY = "_dd.parent_id" +DEFAULT_LAST_PARENT_ID = "0000000000000000" DEFAULT_SERVICE_NAME = "unnamed-python-service" # Used to set the name of an integration on a span COMPONENT = "component" diff --git a/ddtrace/propagation/http.py b/ddtrace/propagation/http.py index c0768194d8b..6ad0c86d41d 100644 --- a/ddtrace/propagation/http.py +++ b/ddtrace/propagation/http.py @@ -38,6 +38,7 @@ from ..internal.compat import ensure_text from ..internal.constants import _PROPAGATION_STYLE_NONE from ..internal.constants import _PROPAGATION_STYLE_W3C_TRACECONTEXT +from ..internal.constants import DEFAULT_LAST_PARENT_ID from ..internal.constants import HIGHER_ORDER_TRACE_ID_BITS as _HIGHER_ORDER_TRACE_ID_BITS from ..internal.constants import LAST_DD_PARENT_ID_KEY from ..internal.constants import MAX_UINT_64BITS as _MAX_UINT_64BITS @@ -700,7 +701,7 @@ def _get_traceparent_values(tp): @staticmethod def _get_tracestate_values(ts_l): - # type: (List[str]) -> Tuple[Optional[int], Dict[str, str], Optional[str], Optional[str]] + # type: (List[str]) -> Tuple[Optional[int], Dict[str, str], Optional[str], str] # tracestate list parsing example: ["dd=s:2;o:rum;t.dm:-4;t.usr.id:baz64","congo=t61rcWkgMzE"] # -> 2, {"_dd.p.dm":"-4","_dd.p.usr.id":"baz64"}, "rum" @@ -727,7 +728,7 @@ def _get_tracestate_values(ts_l): origin = _TraceContext.decode_tag_val(origin) # Get last datadog parent id, this field is used to reconnect traces with missing spans - lpid = dd.get("p", "0000000000000000") + lpid = dd.get("p", DEFAULT_LAST_PARENT_ID) # need to convert from t. to _dd.p. other_propagated_tags = { @@ -736,7 +737,7 @@ def _get_tracestate_values(ts_l): return sampling_priority_ts_int, other_propagated_tags, origin, lpid else: - return None, {}, None, None + return None, {}, None, DEFAULT_LAST_PARENT_ID @staticmethod def _get_sampling_priority( @@ -820,8 +821,7 @@ def _get_context(trace_id, span_id, trace_flag, ts, meta=None): if tracestate_values: sampling_priority_ts, other_propagated_tags, origin, lpid = tracestate_values meta.update(other_propagated_tags.items()) - if lpid: - meta[LAST_DD_PARENT_ID_KEY] = lpid + meta[LAST_DD_PARENT_ID_KEY] = lpid sampling_priority = _TraceContext._get_sampling_priority(trace_flag, sampling_priority_ts, origin) else: @@ -921,6 +921,10 @@ def _resolve_contexts(contexts, styles_w_ctx, normalized_headers): ts = _extract_header_value(_POSSIBLE_HTTP_HEADER_TRACESTATE, normalized_headers) if ts: primary_context._meta[W3C_TRACESTATE_KEY] = ts + # Ensure the last datadog parent id is always set on the primary context + primary_context._meta[LAST_DD_PARENT_ID_KEY] = context._meta.get( + LAST_DD_PARENT_ID_KEY, DEFAULT_LAST_PARENT_ID + ) primary_context._span_links = links return primary_context diff --git a/tests/tracer/test_propagation.py b/tests/tracer/test_propagation.py index e6c263e5d83..4c3071e61b0 100644 --- a/tests/tracer/test_propagation.py +++ b/tests/tracer/test_propagation.py @@ -692,7 +692,7 @@ def test_get_wsgi_header(tracer): # noqa: F811 TRACECONTEXT_HEADERS_VALID_64_bit = { _HTTP_HEADER_TRACEPARENT: "00-000000000000000064fe8b2a57d3eff7-00f067aa0ba902b7-01", - _HTTP_HEADER_TRACESTATE: "dd=s:2;o:rum;t.dm:-4;t.usr.id:baz64,congo=t61rcWkgMzE", + _HTTP_HEADER_TRACESTATE: "dd=s:2;o:rum;p:00f067aa0ba902b7;t.dm:-4;t.usr.id:baz64,congo=t61rcWkgMzE", } @@ -937,7 +937,7 @@ def test_extract_traceparent(caplog, headers, expected_tuple, expected_logging, ( "congo=t61rcWkgMzE,mako=s:2;o:rum;", # sampling_priority_ts, other_propagated_tags, origin, parent id - (None, {}, None, None), + (None, {}, None, "0000000000000000"), None, None, ), @@ -1754,7 +1754,11 @@ def test_extract_tracecontext(headers, expected_context): "span_id": 5678, "sampling_priority": 1, "dd_origin": "synthetics", - "meta": {"tracestate": TRACECONTEXT_HEADERS_VALID[_HTTP_HEADER_TRACESTATE], "_dd.p.dm": "-3"}, + "meta": { + "tracestate": DATADOG_TRACECONTEXT_MATCHING_TRACE_ID_HEADERS[_HTTP_HEADER_TRACESTATE], + "_dd.p.dm": "-3", + LAST_DD_PARENT_ID_KEY: "00f067aa0ba902b7", + }, }, ), # testing that tracestate is not added when tracecontext style comes later and does not match first style's trace-id @@ -2002,11 +2006,11 @@ def test_DD_TRACE_PROPAGATION_STYLE_EXTRACT_overrides_DD_TRACE_PROPAGATION_STYLE span_id=67667974448284343, meta={ "traceparent": "00-000000000000000064fe8b2a57d3eff7-00f067aa0ba902b7-01", - "tracestate": TRACECONTEXT_HEADERS_VALID[_HTTP_HEADER_TRACESTATE], + "tracestate": ALL_HEADERS_CHAOTIC_2[_HTTP_HEADER_TRACESTATE], "_dd.p.dm": "-4", "_dd.p.usr.id": "baz64", "_dd.origin": "rum", - LAST_DD_PARENT_ID_KEY: "0000000000000000", + LAST_DD_PARENT_ID_KEY: "00f067aa0ba902b7", }, metrics={"_sampling_priority_v1": 2}, span_links=[ @@ -2117,7 +2121,8 @@ def test_DD_TRACE_PROPAGATION_STYLE_EXTRACT_overrides_DD_TRACE_PROPAGATION_STYLE meta={ "_dd.p.dm": "-3", "_dd.origin": "synthetics", - "tracestate": "dd=s:2;o:rum;t.dm:-4;t.usr.id:baz64,congo=t61rcWkgMzE", + "tracestate": ALL_HEADERS_CHAOTIC_1[_HTTP_HEADER_TRACESTATE], + LAST_DD_PARENT_ID_KEY: "00f067aa0ba902b7", }, metrics={"_sampling_priority_v1": 1}, span_links=[ From 64658dc6be2330c064c523c13ad80e38441c4a91 Mon Sep 17 00:00:00 2001 From: Emmett Butler <723615+emmettbutler@users.noreply.github.com> Date: Wed, 8 May 2024 06:19:10 -0700 Subject: [PATCH 008/104] chore: different lifetimes for PRs and issues (#9199) This change makes inactive issues get closed at a slower rate than inactive pull requests, reflecting actual usage patterns of this repo. ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .github/workflows/stale.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 9a9a7f672a8..63a045d498b 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -17,7 +17,8 @@ jobs: # DEV: GitHub Actions have an API rate limit of 1000 operations per hour per repository # This limit is shared across all actions operations-per-run: 200 - days-before-close: 30 + days-before-pr-close: 30 + days-before-issue-close: 90 exempt-issue-labels: 'proposal' exempt-pr-labels: 'proposal' close-issue-message: | From 8d678697b35e8206237f134c697a45572df80d78 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Wed, 8 May 2024 15:54:37 +0200 Subject: [PATCH 009/104] chore(iast): redaction algorithms refactor II (#9163) # Summarize Refactor of the IAST redaction system. The old algorithms had several problems: ## Description This PR continues this https://github.com/DataDog/dd-trace-py/pull/9126 - Migrate SQL Injection to this new algorithm - Remove deprecated code ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. - [x] If change touches code that signs or publishes builds or packages, or handles credentials of any kind, I've requested a review from `@DataDog/security-design-and-guidance`. ## Reviewer Checklist - [ ] Title is accurate - [ ] All changes are related to the pull request's stated goal - [ ] Description motivates each change - [ ] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [ ] Testing strategy adequately addresses listed risks - [ ] Change is maintainable (easy to change, telemetry, documentation) - [ ] Release note makes sense to a user of the library - [ ] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [ ] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../_evidence_redaction/_sensitive_handler.py | 7 +- .../sql_sensitive_analyzer.py | 70 ++++ ddtrace/appsec/_iast/_taint_dict.py | 21 -- .../appsec/_iast/_taint_tracking/aspects.py | 1 - ddtrace/appsec/_iast/_taint_utils.py | 4 +- ddtrace/appsec/_iast/_utils.py | 90 +----- ddtrace/appsec/_iast/constants.py | 14 +- ddtrace/appsec/_iast/reporter.py | 20 +- ddtrace/appsec/_iast/taint_sinks/_base.py | 159 +-------- .../_iast/taint_sinks/command_injection.py | 3 - .../_iast/taint_sinks/header_injection.py | 14 - .../_iast/taint_sinks/insecure_cookie.py | 4 - .../appsec/_iast/taint_sinks/sql_injection.py | 164 ---------- ddtrace/appsec/_iast/taint_sinks/ssrf.py | 3 - .../appsec/_iast/taint_sinks/weak_cipher.py | 2 - ddtrace/appsec/_iast/taint_sinks/weak_hash.py | 2 - .../_iast/taint_sinks/weak_randomness.py | 2 - ddtrace/contrib/dbapi/__init__.py | 6 +- ddtrace/contrib/dbapi_async/__init__.py | 6 +- .../test_header_injection_redacted.py | 61 ++-- .../test_path_traversal_redacted.py | 83 +++-- .../iast/taint_sinks/test_sql_injection.py | 29 +- .../test_sql_injection_redacted.py | 305 ++++++++++-------- tests/appsec/iast/test_taint_utils.py | 16 +- tests/contrib/dbapi/test_dbapi_appsec.py | 2 +- .../contrib/django/django_app/appsec_urls.py | 4 +- .../contrib/django/test_django_appsec_iast.py | 180 +++++------ tests/contrib/flask/test_flask_appsec_iast.py | 11 +- 28 files changed, 476 insertions(+), 807 deletions(-) create mode 100644 ddtrace/appsec/_iast/_evidence_redaction/sql_sensitive_analyzer.py delete mode 100644 ddtrace/appsec/_iast/_taint_dict.py diff --git a/ddtrace/appsec/_iast/_evidence_redaction/_sensitive_handler.py b/ddtrace/appsec/_iast/_evidence_redaction/_sensitive_handler.py index b76ad6c96b1..c41e56ca1c3 100644 --- a/ddtrace/appsec/_iast/_evidence_redaction/_sensitive_handler.py +++ b/ddtrace/appsec/_iast/_evidence_redaction/_sensitive_handler.py @@ -3,11 +3,14 @@ from ddtrace.internal.logger import get_logger from ddtrace.settings.asm import config as asm_config +from .._utils import _get_source_index from ..constants import VULN_CMDI from ..constants import VULN_HEADER_INJECTION +from ..constants import VULN_SQL_INJECTION from ..constants import VULN_SSRF from .command_injection_sensitive_analyzer import command_injection_sensitive_analyzer from .header_injection_sensitive_analyzer import header_injection_sensitive_analyzer +from .sql_sensitive_analyzer import sql_sensitive_analyzer from .url_sensitive_analyzer import url_sensitive_analyzer @@ -27,7 +30,7 @@ def __init__(self): self._sensitive_analyzers = { VULN_CMDI: command_injection_sensitive_analyzer, - # SQL_INJECTION: sql_sensitive_analyzer, + VULN_SQL_INJECTION: sql_sensitive_analyzer, VULN_SSRF: url_sensitive_analyzer, VULN_HEADER_INJECTION: header_injection_sensitive_analyzer, } @@ -178,7 +181,7 @@ def to_redacted_json(self, evidence_value, sensitive, tainted_ranges, sources): if next_tainted and next_tainted["start"] == i: self.write_value_part(value_parts, evidence_value[start:i], source_index) - source_index = next_tainted_index + source_index = _get_source_index(sources, next_tainted["source"]) while next_sensitive and self._contains(next_tainted, next_sensitive): redaction_start = next_sensitive["start"] - next_tainted["start"] diff --git a/ddtrace/appsec/_iast/_evidence_redaction/sql_sensitive_analyzer.py b/ddtrace/appsec/_iast/_evidence_redaction/sql_sensitive_analyzer.py new file mode 100644 index 00000000000..7410ec46b4a --- /dev/null +++ b/ddtrace/appsec/_iast/_evidence_redaction/sql_sensitive_analyzer.py @@ -0,0 +1,70 @@ +import re + +from ddtrace.appsec._iast.constants import DBAPI_MARIADB +from ddtrace.appsec._iast.constants import DBAPI_MYSQL +from ddtrace.appsec._iast.constants import DBAPI_PSYCOPG +from ddtrace.appsec._iast.constants import DBAPI_SQLITE +from ddtrace.internal.logger import get_logger + + +log = get_logger(__name__) + + +STRING_LITERAL = r"'(?:''|[^'])*'" +POSTGRESQL_ESCAPED_LITERAL = r"\$([^$]*)\$.*?\$\1\$" +MYSQL_STRING_LITERAL = r'"(?:\\\\"|[^"])*"|\'(?:\\\\\'|[^\'])*\'' +LINE_COMMENT = r"--.*$" +BLOCK_COMMENT = r"/\*[\s\S]*?\*/" +EXPONENT = r"(?:E[-+]?\\d+[fd]?)?" +INTEGER_NUMBER = r"(? start + 1: + next_char = evidence.value[start + 1] + if start_char == "/" and next_char == "*": + start += 2 + end -= 2 + elif start_char == "-" and start_char == next_char: + start += 2 + elif start_char.lower() == "q" and next_char == "'": + start += 3 + end -= 2 + elif start_char == "$": + match = regex_result.group(0) + size = match.find("$", 1) + 1 + if size > 1: + start += size + end -= size + tokens.append({"start": start, "end": end}) + regex_result = pattern.search(evidence.value, regex_result.end()) + return tokens diff --git a/ddtrace/appsec/_iast/_taint_dict.py b/ddtrace/appsec/_iast/_taint_dict.py deleted file mode 100644 index 97df240d4d0..00000000000 --- a/ddtrace/appsec/_iast/_taint_dict.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python3 -# -from typing import TYPE_CHECKING # noqa:F401 - - -if TYPE_CHECKING: - from typing import Dict # noqa:F401 - from typing import Tuple # noqa:F401 - - from ._taint_tracking import Source # noqa:F401 - -_IAST_TAINT_DICT = {} # type: Dict[int, Tuple[Tuple[Source, int, int],...]] - - -def get_taint_dict(): # type: () -> Dict[int, Tuple[Tuple[Source, int, int],...]] - return _IAST_TAINT_DICT - - -def clear_taint_mapping(): # type: () -> None - global _IAST_TAINT_DICT - _IAST_TAINT_DICT = {} diff --git a/ddtrace/appsec/_iast/_taint_tracking/aspects.py b/ddtrace/appsec/_iast/_taint_tracking/aspects.py index cae1e07d455..3c0465884ce 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/aspects.py +++ b/ddtrace/appsec/_iast/_taint_tracking/aspects.py @@ -506,7 +506,6 @@ def format_value_aspect( if options == 115: new_text = str_aspect(str, 0, element) elif options == 114: - # TODO: use our repr once we have implemented it new_text = repr_aspect(repr, 0, element) elif options == 97: new_text = ascii(element) diff --git a/ddtrace/appsec/_iast/_taint_utils.py b/ddtrace/appsec/_iast/_taint_utils.py index 8f91c44ff5c..07e5199cb01 100644 --- a/ddtrace/appsec/_iast/_taint_utils.py +++ b/ddtrace/appsec/_iast/_taint_utils.py @@ -5,11 +5,11 @@ from typing import Optional from typing import Union +from ddtrace.appsec._iast.constants import DBAPI_INTEGRATIONS from ddtrace.internal.logger import get_logger from ddtrace.settings.asm import config as asm_config -DBAPI_INTEGRATIONS = ("sqlite", "psycopg", "mysql", "mariadb") DBAPI_PREFIXES = ("django-",) log = get_logger(__name__) @@ -529,7 +529,7 @@ def supported_dbapi_integration(integration_name): return integration_name in DBAPI_INTEGRATIONS or integration_name.startswith(DBAPI_PREFIXES) -def check_tainted_args(args, kwargs, tracer, integration_name, method): +def check_tainted_dbapi_args(args, kwargs, tracer, integration_name, method): if supported_dbapi_integration(integration_name) and method.__name__ == "execute": from ._taint_tracking import is_pyobject_tainted diff --git a/ddtrace/appsec/_iast/_utils.py b/ddtrace/appsec/_iast/_utils.py index 7272abb9016..c1b72f28f04 100644 --- a/ddtrace/appsec/_iast/_utils.py +++ b/ddtrace/appsec/_iast/_utils.py @@ -1,19 +1,10 @@ -import re -import string import sys -from typing import TYPE_CHECKING # noqa:F401 +from typing import List from ddtrace.internal.logger import get_logger from ddtrace.settings.asm import config as asm_config -if TYPE_CHECKING: - from typing import Any # noqa:F401 - from typing import List # noqa:F401 - from typing import Set # noqa:F401 - from typing import Tuple # noqa:F401 - - def _is_python_version_supported(): # type: () -> bool # IAST supports Python versions 3.6 to 3.12 return (3, 6, 0) <= sys.version_info < (3, 13, 0) @@ -31,78 +22,13 @@ def _is_iast_enabled(): return True -# Used to cache the compiled regular expression -_SOURCE_NAME_SCRUB = None -_SOURCE_VALUE_SCRUB = None -_SOURCE_NUMERAL_SCRUB = None - - -def _has_to_scrub(s): # type: (str) -> bool - # TODO: This function is deprecated. - # Redaction migrated to `ddtrace.appsec._iast._evidence_redaction._sensitive_handler` but we need to migrate - # all vulnerabilities to use it first. - global _SOURCE_NAME_SCRUB - global _SOURCE_VALUE_SCRUB - global _SOURCE_NUMERAL_SCRUB - - if _SOURCE_NAME_SCRUB is None: - _SOURCE_NAME_SCRUB = re.compile(asm_config._iast_redaction_name_pattern) - _SOURCE_VALUE_SCRUB = re.compile(asm_config._iast_redaction_value_pattern) - _SOURCE_NUMERAL_SCRUB = re.compile(asm_config._iast_redaction_numeral_pattern) - - return ( - _SOURCE_NAME_SCRUB.match(s) is not None - or _SOURCE_VALUE_SCRUB.match(s) is not None - or _SOURCE_NUMERAL_SCRUB.match(s) is not None - ) - - -def _is_numeric(s): - # TODO: This function is deprecated. - # Redaction migrated to `ddtrace.appsec._iast._evidence_redaction._sensitive_handler` but we need to migrate - # all vulnerabilities to use it first. - global _SOURCE_NUMERAL_SCRUB - - if _SOURCE_NUMERAL_SCRUB is None: - _SOURCE_NUMERAL_SCRUB = re.compile(asm_config._iast_redaction_numeral_pattern) - - return _SOURCE_NUMERAL_SCRUB.match(s) is not None - - -_REPLACEMENTS = string.ascii_letters -_LEN_REPLACEMENTS = len(_REPLACEMENTS) - - -def _scrub(s, has_range=False): # type: (str, bool) -> str - # TODO: This function is deprecated. - # Redaction migrated to `ddtrace.appsec._iast._evidence_redaction._sensitive_handler` but we need to migrate - # all vulnerabilities to use it first. - if has_range: - return "".join([_REPLACEMENTS[i % _LEN_REPLACEMENTS] for i in range(len(s))]) - return "*" * len(s) - - -def _is_evidence_value_parts(value): # type: (Any) -> bool - # TODO: This function is deprecated. - # Redaction migrated to `ddtrace.appsec._iast._evidence_redaction._sensitive_handler` but we need to migrate - # all vulnerabilities to use it first. - return isinstance(value, (set, list)) - - -def _scrub_get_tokens_positions(text, tokens): - # type: (str, Set[str]) -> List[Tuple[int, int]] - # TODO: This function is deprecated. - # Redaction migrated to `ddtrace.appsec._iast._evidence_redaction._sensitive_handler` but we need to migrate - # all vulnerabilities to use it first. - token_positions = [] - - for token in tokens: - position = text.find(token) - if position != -1: - token_positions.append((position, position + len(token))) - - token_positions.sort() - return token_positions +def _get_source_index(sources: List, source) -> int: + i = 0 + for source_ in sources: + if hash(source_) == hash(source): + return i + i += 1 + return -1 def _get_patched_code(module_path, module_name): # type: (str, str) -> str diff --git a/ddtrace/appsec/_iast/constants.py b/ddtrace/appsec/_iast/constants.py index 17981bccbcc..60c864f59ec 100644 --- a/ddtrace/appsec/_iast/constants.py +++ b/ddtrace/appsec/_iast/constants.py @@ -16,15 +16,6 @@ VULNERABILITY_TOKEN_TYPE = Dict[int, Dict[str, Any]] -EVIDENCE_ALGORITHM_TYPE = "ALGORITHM" -EVIDENCE_SQL_INJECTION = "SQL_INJECTION" -EVIDENCE_PATH_TRAVERSAL = "PATH_TRAVERSAL" -EVIDENCE_WEAK_RANDOMNESS = "WEAK_RANDOMNESS" -EVIDENCE_COOKIE = "COOKIE" -EVIDENCE_CMDI = "COMMAND" -EVIDENCE_HEADER_INJECTION = "HEADER_INJECTION" -EVIDENCE_SSRF = "SSRF" - HEADER_NAME_VALUE_SEPARATOR = ": " MD5_DEF = "md5" @@ -91,3 +82,8 @@ "tarfile": {"open"}, "zipfile": {"ZipFile"}, } +DBAPI_SQLITE = "sqlite" +DBAPI_PSYCOPG = "psycopg" +DBAPI_MYSQL = "mysql" +DBAPI_MARIADB = "mariadb" +DBAPI_INTEGRATIONS = (DBAPI_SQLITE, DBAPI_PSYCOPG, DBAPI_MYSQL, DBAPI_MARIADB) diff --git a/ddtrace/appsec/_iast/reporter.py b/ddtrace/appsec/_iast/reporter.py index fa2cc8ae96c..90d334277bd 100644 --- a/ddtrace/appsec/_iast/reporter.py +++ b/ddtrace/appsec/_iast/reporter.py @@ -13,6 +13,7 @@ import attr from ddtrace.appsec._iast._evidence_redaction import sensitive_handler +from ddtrace.appsec._iast._utils import _get_source_index from ddtrace.appsec._iast.constants import VULN_INSECURE_HASHING_TYPE from ddtrace.appsec._iast.constants import VULN_WEAK_CIPHER_TYPE from ddtrace.appsec._iast.constants import VULN_WEAK_RANDOMNESS @@ -26,8 +27,12 @@ def _only_if_true(value): return value if value else None +ATTRS_TO_SKIP = frozenset({"_ranges", "_evidences_with_no_sources", "dialect"}) + + @attr.s(eq=False, hash=False) class Evidence(object): + dialect = attr.ib(type=str, default=None) # type: Optional[str] value = attr.ib(type=str, default=None) # type: Optional[str] _ranges = attr.ib(type=dict, default={}) # type: Any valueParts = attr.ib(type=list, default=None) # type: Any @@ -143,14 +148,6 @@ def add_ranges_to_evidence_and_extract_sources(self, vuln): if source not in self.sources: self.sources = self.sources + [source] - def _get_source_index(self, sources: List[Source], source: Source) -> int: - i = 0 - for source_ in sources: - if hash(source_) == hash(source): - return i - i += 1 - return -1 - def build_and_scrub_value_parts(self) -> Dict[str, Any]: """ Builds and scrubs value parts of vulnerabilities. @@ -197,7 +194,7 @@ def get_unredacted_value_parts(self, evidence_value: str, ranges: List[dict], so if from_index < range_["start"]: value_parts.append({"value": evidence_value[from_index : range_["start"]]}) - source_index = self._get_source_index(sources, range_["source"]) + source_index = _get_source_index(sources, range_["source"]) value_parts.append( {"value": evidence_value[range_["start"] : range_["end"]], "source": source_index} # type: ignore[dict-item] @@ -217,7 +214,10 @@ def _to_dict(self) -> Dict[str, Any]: Returns: - Dict[str, Any]: Dictionary representation of the IAST span reporter. """ - return attr.asdict(self, filter=lambda attr, x: x is not None and attr.name != "_ranges") + return attr.asdict( + self, + filter=lambda attr, x: x is not None and attr.name not in ATTRS_TO_SKIP, + ) def _to_str(self) -> str: """ diff --git a/ddtrace/appsec/_iast/taint_sinks/_base.py b/ddtrace/appsec/_iast/taint_sinks/_base.py index 215e2abb208..50e025f393c 100644 --- a/ddtrace/appsec/_iast/taint_sinks/_base.py +++ b/ddtrace/appsec/_iast/taint_sinks/_base.py @@ -1,20 +1,18 @@ import os -from typing import TYPE_CHECKING # noqa:F401 -from typing import cast # noqa:F401 +from typing import Any +from typing import Callable +from typing import Optional +from typing import Text from ddtrace import tracer from ddtrace.appsec._constants import IAST from ddtrace.internal import core from ddtrace.internal.logger import get_logger from ddtrace.internal.utils.cache import LFUCache -from ddtrace.settings.asm import config as asm_config from ..._deduplications import deduplication from .._overhead_control_engine import Operation from .._stacktrace import get_info_frame -from .._utils import _has_to_scrub -from .._utils import _is_evidence_value_parts -from .._utils import _scrub from ..processor import AppSecIastSpanProcessor from ..reporter import Evidence from ..reporter import IastSpanReporter @@ -22,16 +20,6 @@ from ..reporter import Vulnerability -if TYPE_CHECKING: # pragma: no cover - from typing import Any # noqa:F401 - from typing import Callable # noqa:F401 - from typing import Dict # noqa:F401 - from typing import List # noqa:F401 - from typing import Optional # noqa:F401 - from typing import Set # noqa:F401 - from typing import Text # noqa:F401 - from typing import Union # noqa:F401 - log = get_logger(__name__) CWD = os.path.abspath(os.getcwd()) @@ -39,8 +27,8 @@ class taint_sink_deduplication(deduplication): def _extract(self, args): - # we skip 0, 1 and last position because its the cls, span and sources respectively - return args[2:-1] + # We skip positions 0 and 1 because they represent the 'cls' and 'span' respectively + return args[2:] def _check_positions_contained(needle, container): @@ -57,7 +45,6 @@ def _check_positions_contained(needle, container): class VulnerabilityBase(Operation): vulnerability_type = "" - evidence_type = "" _redacted_report_cache = LFUCache() @classmethod @@ -66,10 +53,8 @@ def _reset_cache_for_testing(cls): cls._redacted_report_cache.clear() @classmethod - def wrap(cls, func): - # type: (Callable) -> Callable - def wrapper(wrapped, instance, args, kwargs): - # type: (Callable, Any, Any, Any) -> Any + def wrap(cls, func: Callable) -> Callable: + def wrapper(wrapped: Callable, instance: Any, args: Any, kwargs: Any) -> Any: """Get the current root Span and attach it to the wrapped function. We need the span to report the vulnerability and update the context with the report information. """ @@ -83,7 +68,7 @@ def wrapper(wrapped, instance, args, kwargs): @classmethod @taint_sink_deduplication - def _prepare_report(cls, span, vulnerability_type, evidence, file_name, line_number, sources): + def _prepare_report(cls, span, vulnerability_type, evidence, file_name, line_number): if line_number is not None and (line_number == 0 or line_number < -1): line_number = -1 @@ -99,19 +84,12 @@ def _prepare_report(cls, span, vulnerability_type, evidence, file_name, line_num report = IastSpanReporter(vulnerabilities={vulnerability}) report.add_ranges_to_evidence_and_extract_sources(vulnerability) - if getattr(cls, "redact_report", False): - redacted_report = cls._redacted_report_cache.get( - hash(report), lambda x: cls._redact_report(cast(IastSpanReporter, report)) - ) - else: - redacted_report = report - core.set_item(IAST.CONTEXT_KEY, redacted_report, span=span) + core.set_item(IAST.CONTEXT_KEY, report, span=span) return True @classmethod - def report(cls, evidence_value="", value_parts=None, sources=None): - # type: (Any, Any, Optional[List[Any]]) -> None + def report(cls, evidence_value: Text = "", dialect: Optional[Text] = None) -> None: """Build a IastSpanReporter instance to report it in the `AppSecIastSpanProcessor` as a string JSON""" # TODO: type of evidence_value will be Text. We wait to finish the redaction refactor. if cls.acquire_quota(): @@ -146,122 +124,15 @@ def report(cls, evidence_value="", value_parts=None, sources=None): if not cls.is_not_reported(file_name, line_number): return - - # TODO: This function is deprecated, but we need to migrate all vulnerabilities first before deleting it - if _is_evidence_value_parts(evidence_value) or _is_evidence_value_parts(value_parts): - evidence = Evidence(value=evidence_value, valueParts=value_parts) # Evidence is a string in weak cipher, weak hash and weak randomness - elif isinstance(evidence_value, (str, bytes, bytearray)): - evidence = Evidence(value=evidence_value) # type: ignore + if isinstance(evidence_value, (str, bytes, bytearray)): + evidence = Evidence(value=evidence_value, dialect=dialect) else: log.debug("Unexpected evidence_value type: %s", type(evidence_value)) - evidence = Evidence(value="") + evidence = Evidence(value="", dialect=dialect) - result = cls._prepare_report(span, cls.vulnerability_type, evidence, file_name, line_number, sources) + result = cls._prepare_report(span, cls.vulnerability_type, evidence, file_name, line_number) # If result is None that's mean deduplication raises and no vulnerability wasn't reported, with that, # we need to restore the quota if not result: cls.increment_quota() - - @classmethod - def _extract_sensitive_tokens(cls, report): - # type: (Dict[Vulnerability, str]) -> Dict[int, Dict[str, Any]] - # TODO: This function is deprecated. - # Redaction migrated to `ddtrace.appsec._iast._evidence_redaction._sensitive_handler` but we need to migrate - # all vulnerabilities to use it first. - log.debug("Base class VulnerabilityBase._extract_sensitive_tokens called") - return {} - - @classmethod - def _get_vulnerability_text(cls, vulnerability): - # TODO: This function is deprecated. - # Redaction migrated to `ddtrace.appsec._iast._evidence_redaction._sensitive_handler` but we need to migrate - # all vulnerabilities to use it first. - if vulnerability and vulnerability.evidence.value is not None: - return vulnerability.evidence.value - - if vulnerability.evidence.valueParts is not None: - return "".join( - [ - (part.get("value", "") if type(part) is not str else part) - for part in vulnerability.evidence.valueParts - ] - ) - - return "" - - @classmethod - def replace_tokens( - cls, - vuln, - vulns_to_tokens, - has_range=False, - ): - # TODO: This function is deprecated. - # Redaction migrated to `ddtrace.appsec._iast._evidence_redaction._sensitive_handler` but we need to migrate - # all vulnerabilities to use it first. - ret = vuln.evidence.value - replaced = False - - for token in vulns_to_tokens[hash(vuln)]["tokens"]: - ret = ret.replace(token, _scrub(token, has_range)) - replaced = True - - return ret, replaced - - @classmethod - def _custom_edit_valueparts(cls, vuln): - # Subclasses could optionally implement this to add further processing to the - # vulnerability valueParts - # TODO: This function is deprecated. - # Redaction migrated to `ddtrace.appsec._iast._evidence_redaction._sensitive_handler` but we need to migrate - # all vulnerabilities to use it first. - return - - @classmethod - def _redact_report(cls, report): # type: (IastSpanReporter) -> IastSpanReporter - # TODO: This function is deprecated. - # Redaction migrated to `ddtrace.appsec._iast._evidence_redaction._sensitive_handler` but we need to migrate - # all vulnerabilities to use it first. - if not asm_config._iast_redaction_enabled: - return report - - # See if there is a match on either any of the sources or value parts of the report - already_scrubbed = {} - - sources_values_to_scrubbed = {} - vulns_to_text = {vuln: cls._get_vulnerability_text(vuln) for vuln in report.vulnerabilities} - vulns_to_tokens = cls._extract_sensitive_tokens(vulns_to_text) - - for source in report.sources: - # Join them so we only run the regexps once for each source - # joined_fields = "%s%s" % (source.name, source.value) - if _has_to_scrub(source.name) or _has_to_scrub(source.value): # type: ignore - scrubbed = _scrub(source.value, has_range=True) # type: ignore - already_scrubbed[source.value] = scrubbed - source.redacted = True - sources_values_to_scrubbed[source.value] = scrubbed - source.pattern = scrubbed - source.value = None - - already_scrubbed_set = set(already_scrubbed.keys()) - for vuln in report.vulnerabilities: - if vuln.evidence.value is not None: - pattern, replaced = cls.replace_tokens(vuln, vulns_to_tokens, hasattr(vuln.evidence.value, "source")) - if replaced: - vuln.evidence.value = None - - if vuln.evidence.valueParts is None: - continue - for part in vuln.evidence.valueParts: - part_value = part.get("value") - if not part_value: - continue - - if part_value in already_scrubbed_set: - part["pattern"] = already_scrubbed[part["value"]] - part["redacted"] = True - del part["value"] - - cls._custom_edit_valueparts(vuln) - return report diff --git a/ddtrace/appsec/_iast/taint_sinks/command_injection.py b/ddtrace/appsec/_iast/taint_sinks/command_injection.py index 8f123a2be4c..c95d99567cd 100644 --- a/ddtrace/appsec/_iast/taint_sinks/command_injection.py +++ b/ddtrace/appsec/_iast/taint_sinks/command_injection.py @@ -74,9 +74,6 @@ def _iast_cmdi_subprocess_init(wrapped, instance, args, kwargs): @oce.register class CommandInjection(VulnerabilityBase): vulnerability_type = VULN_CMDI - # TODO: Redaction migrated to `ddtrace.appsec._iast._evidence_redaction._sensitive_handler` but we need to migrate - # all vulnerabilities to use it first. - redact_report = False def _iast_report_cmdi(shell_args: Union[str, List[str]]) -> None: diff --git a/ddtrace/appsec/_iast/taint_sinks/header_injection.py b/ddtrace/appsec/_iast/taint_sinks/header_injection.py index 1ce8a52d5e4..7fa0b6111dd 100644 --- a/ddtrace/appsec/_iast/taint_sinks/header_injection.py +++ b/ddtrace/appsec/_iast/taint_sinks/header_injection.py @@ -1,5 +1,3 @@ -import re - from ddtrace.internal.logger import get_logger from ddtrace.settings.asm import config as asm_config @@ -19,15 +17,6 @@ log = get_logger(__name__) -_HEADERS_NAME_REGEXP = re.compile( - r"(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?|access_?|secret_?)key(?:_?id)?|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)", - re.IGNORECASE, -) -_HEADERS_VALUE_REGEXP = re.compile( - r"(?:bearer\\s+[a-z0-9\\._\\-]+|glpat-[\\w\\-]{20}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L][\\w=\\-]+\\.ey[I-L][\\w=\\-]+(?:\\.[\\w.+/=\\-]+)?|(?:[\\-]{5}BEGIN[a-z\\s]+PRIVATE\\sKEY[\\-]{5}[^\\-]+[\\-]{5}END[a-z\\s]+PRIVATE\\sKEY[\\-]{5}|ssh-rsa\\s*[a-z0-9/\\.+]{100,}))", - re.IGNORECASE, -) - def get_version(): # type: () -> str @@ -103,9 +92,6 @@ def _iast_h(wrapped, instance, args, kwargs): @oce.register class HeaderInjection(VulnerabilityBase): vulnerability_type = VULN_HEADER_INJECTION - # TODO: Redaction migrated to `ddtrace.appsec._iast._evidence_redaction._sensitive_handler` but we need to migrate - # all vulnerabilities to use it first. - redact_report = False def _iast_report_header_injection(headers_args) -> None: diff --git a/ddtrace/appsec/_iast/taint_sinks/insecure_cookie.py b/ddtrace/appsec/_iast/taint_sinks/insecure_cookie.py index bb8147760a7..e188e2ecbe4 100644 --- a/ddtrace/appsec/_iast/taint_sinks/insecure_cookie.py +++ b/ddtrace/appsec/_iast/taint_sinks/insecure_cookie.py @@ -4,7 +4,6 @@ from .. import oce from .._metrics import _set_metric_iast_executed_sink from .._metrics import increment_iast_span_metric -from ..constants import EVIDENCE_COOKIE from ..constants import VULN_INSECURE_COOKIE from ..constants import VULN_NO_HTTPONLY_COOKIE from ..constants import VULN_NO_SAMESITE_COOKIE @@ -19,7 +18,6 @@ @oce.register class InsecureCookie(VulnerabilityBase): vulnerability_type = VULN_INSECURE_COOKIE - evidence_type = EVIDENCE_COOKIE scrub_evidence = False skip_location = True @@ -27,14 +25,12 @@ class InsecureCookie(VulnerabilityBase): @oce.register class NoHttpOnlyCookie(VulnerabilityBase): vulnerability_type = VULN_NO_HTTPONLY_COOKIE - evidence_type = EVIDENCE_COOKIE skip_location = True @oce.register class NoSameSite(VulnerabilityBase): vulnerability_type = VULN_NO_SAMESITE_COOKIE - evidence_type = EVIDENCE_COOKIE skip_location = True diff --git a/ddtrace/appsec/_iast/taint_sinks/sql_injection.py b/ddtrace/appsec/_iast/taint_sinks/sql_injection.py index 68d5a289c01..f671d92c387 100644 --- a/ddtrace/appsec/_iast/taint_sinks/sql_injection.py +++ b/ddtrace/appsec/_iast/taint_sinks/sql_injection.py @@ -1,172 +1,8 @@ -import re -from typing import TYPE_CHECKING # noqa:F401 - from .. import oce -from .._taint_tracking import taint_ranges_as_evidence_info -from .._utils import _has_to_scrub -from .._utils import _is_numeric -from .._utils import _scrub_get_tokens_positions -from ..constants import EVIDENCE_SQL_INJECTION from ..constants import VULN_SQL_INJECTION from ._base import VulnerabilityBase -if TYPE_CHECKING: - from typing import Any # noqa:F401 - from typing import Dict # noqa:F401 - - from .reporter import Vulnerability # noqa:F401 - -from sqlparse import parse -from sqlparse import tokens - - -_TEXT_TOKENS_REGEXP = re.compile(r"\b\w+\b") - - @oce.register class SqlInjection(VulnerabilityBase): vulnerability_type = VULN_SQL_INJECTION - evidence_type = EVIDENCE_SQL_INJECTION - redact_report = True - - @classmethod - def report(cls, evidence_value=None, sources=None): - value_parts = [] - if isinstance(evidence_value, (str, bytes, bytearray)): - value_parts, sources = taint_ranges_as_evidence_info(evidence_value) - super(SqlInjection, cls).report(evidence_value=evidence_value, value_parts=value_parts, sources=sources) - - @classmethod - def _extract_sensitive_tokens(cls, vulns_to_text): - # type: (Dict[Vulnerability, str]) -> Dict[int, Dict[str, Any]] - ret = {} # type: Dict[int, Dict[str, Any]] - for vuln, text in vulns_to_text.items(): - vuln_hash = hash(vuln) - ret[vuln_hash] = { - "tokens": set(_TEXT_TOKENS_REGEXP.findall(text)), - } - ret[vuln_hash]["token_positions"] = _scrub_get_tokens_positions(text, ret[vuln_hash]["tokens"]) - - return ret - - @classmethod - def _custom_edit_valueparts(cls, vuln): - def _maybe_with_source(source, value): - if source is not None: - return {"value": value, "source": source} - return {"value": value} - - new_valueparts = [] - - in_singleline_comment = False - - for part in vuln.evidence.valueParts: - source = part.get("source") - value = part.get("value") - - if not value or part.get("redacted"): - new_valueparts.append(part) - continue - - parsed = parse(value)[0].flatten() - out = [] - - for item in parsed: - if item.ttype == tokens.Whitespace.Newline: - in_singleline_comment = False - - elif in_singleline_comment: - # Skip all tokens after a -- comment until newline - continue - - if item.ttype in { - tokens.Literal.String.Single, - tokens.Literal.String.Double, - tokens.Literal.String.Symbol, - tokens.Literal.Number.Integer, - tokens.Literal.Number.Float, - tokens.Literal.Number.Hexadecimal, - tokens.Comment.Single, - tokens.Comment.Multiline, - tokens.Name, - }: - redact_fully = False - add_later = None - sitem = str(item) - - if _is_numeric(sitem): - redact_fully = True - elif item.ttype == tokens.Literal.String.Single or ( - item.ttype == tokens.Literal.String.Symbol and "'" in str(item) - ): - out.append("'") - add_later = "'" - str_item = sitem.replace("'", "") - if _is_numeric(str_item): - redact_fully = True - elif item.ttype == tokens.Literal.String.Double or ( - item.ttype == tokens.Literal.String.Symbol and '"' in str(item) - ): - out.append('"') - add_later = '"' - str_item = sitem.replace('"', "") - if _is_numeric(str_item): - redact_fully = True - elif item.ttype == tokens.Comment.Single: - out.append("--") - add_later = "" - redact_fully = True - in_singleline_comment = True - elif item.ttype == tokens.Comment.Multiline: - out.append("/*") - add_later = "*/" - redact_fully = True - elif item.ttype in (tokens.Number.Integer, tokens.Number.Float, tokens.Number.Hexadecimal): - redact_fully = True - else: - out.append(sitem) - continue - - if len(out): - new_valueparts.append(_maybe_with_source(source, "".join(out))) - - if redact_fully: - # Comments are totally redacted - new_valueparts.append({"redacted": True}) - else: - new_valueparts.append(_maybe_with_source(source, str_item)) - - if add_later: - out = [add_later] - else: - out = [] - else: - out.append(str(item)) - - if len(out): - new_valueparts.append(_maybe_with_source(source, "".join(out))) - - # Scrub as needed - idx = 0 - len_parts = len(new_valueparts) - while idx < len_parts: - value = new_valueparts[idx].get("value") - if value and _has_to_scrub(value) and idx < (len_parts - 1) and "redacted" not in new_valueparts[idx + 1]: - # Scrub the value, which is the next one, except when the previous was a LIKE or an assignment - # in which case this is the value to scrub - prev_valuepart = new_valueparts[idx - 1].get("value", "").strip().lower() - if len(prev_valuepart) and (" like " in prev_valuepart or prev_valuepart[-1] == "="): - new_valueparts[idx] = {"redacted": True} - else: # scrub the next non empty quote value - for part in new_valueparts[idx + 1 :]: - idx += 1 - next_valuepart = part.get("value", "").strip() - if not len(next_valuepart) or next_valuepart in ("'", '"'): - continue - - new_valueparts[idx] = {"redacted": True} - break - idx += 1 - - vuln.evidence.valueParts = new_valueparts diff --git a/ddtrace/appsec/_iast/taint_sinks/ssrf.py b/ddtrace/appsec/_iast/taint_sinks/ssrf.py index b6d1300de7f..40b0f2fdb5e 100644 --- a/ddtrace/appsec/_iast/taint_sinks/ssrf.py +++ b/ddtrace/appsec/_iast/taint_sinks/ssrf.py @@ -16,9 +16,6 @@ @oce.register class SSRF(VulnerabilityBase): vulnerability_type = VULN_SSRF - # TODO: Redaction migrated to `ddtrace.appsec._iast._evidence_redaction._sensitive_handler` but we need to migrate - # all vulnerabilities to use it first. - redact_report = False def _iast_report_ssrf(func: Callable, *args, **kwargs): diff --git a/ddtrace/appsec/_iast/taint_sinks/weak_cipher.py b/ddtrace/appsec/_iast/taint_sinks/weak_cipher.py index 3199528ef03..21a494edf3b 100644 --- a/ddtrace/appsec/_iast/taint_sinks/weak_cipher.py +++ b/ddtrace/appsec/_iast/taint_sinks/weak_cipher.py @@ -15,7 +15,6 @@ from ..constants import BLOWFISH_DEF from ..constants import DEFAULT_WEAK_CIPHER_ALGORITHMS from ..constants import DES_DEF -from ..constants import EVIDENCE_ALGORITHM_TYPE from ..constants import RC2_DEF from ..constants import RC4_DEF from ..constants import VULN_WEAK_CIPHER_TYPE @@ -44,7 +43,6 @@ def get_weak_cipher_algorithms(): @oce.register class WeakCipher(VulnerabilityBase): vulnerability_type = VULN_WEAK_CIPHER_TYPE - evidence_type = EVIDENCE_ALGORITHM_TYPE def unpatch_iast(): diff --git a/ddtrace/appsec/_iast/taint_sinks/weak_hash.py b/ddtrace/appsec/_iast/taint_sinks/weak_hash.py index 9bebaf805be..0932cc9fb05 100644 --- a/ddtrace/appsec/_iast/taint_sinks/weak_hash.py +++ b/ddtrace/appsec/_iast/taint_sinks/weak_hash.py @@ -14,7 +14,6 @@ from .._patch import try_unwrap from .._patch import try_wrap_function_wrapper from ..constants import DEFAULT_WEAK_HASH_ALGORITHMS -from ..constants import EVIDENCE_ALGORITHM_TYPE from ..constants import MD5_DEF from ..constants import SHA1_DEF from ..constants import VULN_INSECURE_HASHING_TYPE @@ -42,7 +41,6 @@ def get_weak_hash_algorithms(): @oce.register class WeakHash(VulnerabilityBase): vulnerability_type = VULN_INSECURE_HASHING_TYPE - evidence_type = EVIDENCE_ALGORITHM_TYPE def unpatch_iast(): diff --git a/ddtrace/appsec/_iast/taint_sinks/weak_randomness.py b/ddtrace/appsec/_iast/taint_sinks/weak_randomness.py index bd7fc6e5051..a9fc130b39f 100644 --- a/ddtrace/appsec/_iast/taint_sinks/weak_randomness.py +++ b/ddtrace/appsec/_iast/taint_sinks/weak_randomness.py @@ -1,5 +1,4 @@ from .. import oce -from ..constants import EVIDENCE_WEAK_RANDOMNESS from ..constants import VULN_WEAK_RANDOMNESS from ._base import VulnerabilityBase @@ -7,7 +6,6 @@ @oce.register class WeakRandomness(VulnerabilityBase): vulnerability_type = VULN_WEAK_RANDOMNESS - evidence_type = EVIDENCE_WEAK_RANDOMNESS @classmethod def report(cls, evidence_value=None, sources=None): diff --git a/ddtrace/contrib/dbapi/__init__.py b/ddtrace/contrib/dbapi/__init__.py index 3fb5d6cbe1c..b0e4d2aec81 100644 --- a/ddtrace/contrib/dbapi/__init__.py +++ b/ddtrace/contrib/dbapi/__init__.py @@ -105,13 +105,13 @@ def _trace_method(self, method, name, resource, extra_tags, dbm_propagator, *arg if _is_iast_enabled(): try: from ddtrace.appsec._iast._metrics import _set_metric_iast_executed_sink - from ddtrace.appsec._iast._taint_utils import check_tainted_args + from ddtrace.appsec._iast._taint_utils import check_tainted_dbapi_args from ddtrace.appsec._iast.taint_sinks.sql_injection import SqlInjection increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, SqlInjection.vulnerability_type) _set_metric_iast_executed_sink(SqlInjection.vulnerability_type) - if check_tainted_args(args, kwargs, pin.tracer, self._self_config.integration_name, method): - SqlInjection.report(evidence_value=args[0]) + if check_tainted_dbapi_args(args, kwargs, pin.tracer, self._self_config.integration_name, method): + SqlInjection.report(evidence_value=args[0], dialect=self._self_config.integration_name) except Exception: log.debug("Unexpected exception while reporting vulnerability", exc_info=True) diff --git a/ddtrace/contrib/dbapi_async/__init__.py b/ddtrace/contrib/dbapi_async/__init__.py index c37638fdf67..abbae3b77a3 100644 --- a/ddtrace/contrib/dbapi_async/__init__.py +++ b/ddtrace/contrib/dbapi_async/__init__.py @@ -77,13 +77,13 @@ async def _trace_method(self, method, name, resource, extra_tags, dbm_propagator if _is_iast_enabled(): from ddtrace.appsec._iast._metrics import _set_metric_iast_executed_sink - from ddtrace.appsec._iast._taint_utils import check_tainted_args + from ddtrace.appsec._iast._taint_utils import check_tainted_dbapi_args from ddtrace.appsec._iast.taint_sinks.sql_injection import SqlInjection increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, SqlInjection.vulnerability_type) _set_metric_iast_executed_sink(SqlInjection.vulnerability_type) - if check_tainted_args(args, kwargs, pin.tracer, self._self_config.integration_name, method): - SqlInjection.report(evidence_value=args[0]) + if check_tainted_dbapi_args(args, kwargs, pin.tracer, self._self_config.integration_name, method): + SqlInjection.report(evidence_value=args[0], dialect=self._self_config.integration_name) # set analytics sample rate if enabled but only for non-FetchTracedCursor if not isinstance(self, FetchTracedAsyncCursor): diff --git a/tests/appsec/iast/taint_sinks/test_header_injection_redacted.py b/tests/appsec/iast/taint_sinks/test_header_injection_redacted.py index db9272e1625..6861d28edbf 100644 --- a/tests/appsec/iast/taint_sinks/test_header_injection_redacted.py +++ b/tests/appsec/iast/taint_sinks/test_header_injection_redacted.py @@ -1,14 +1,17 @@ +from mock.mock import ANY import pytest from ddtrace.appsec._constants import IAST +from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted from ddtrace.appsec._iast._taint_tracking import origin_to_str from ddtrace.appsec._iast._taint_tracking import str_to_origin +from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect from ddtrace.appsec._iast.constants import VULN_HEADER_INJECTION from ddtrace.appsec._iast.reporter import Evidence from ddtrace.appsec._iast.reporter import IastSpanReporter from ddtrace.appsec._iast.reporter import Location -from ddtrace.appsec._iast.reporter import Source from ddtrace.appsec._iast.reporter import Vulnerability from ddtrace.appsec._iast.taint_sinks.header_injection import HeaderInjection from ddtrace.internal import core @@ -24,20 +27,25 @@ ], ) def test_header_injection_redact_excluded(header_name, header_value): - ev = Evidence( - valueParts=[ - {"value": header_name + ": "}, - {"value": header_value, "source": 0}, - ] - ) + header_value_tainted = taint_pyobject(pyobject=header_value, source_name="SomeName", source_value=header_value) + ev = Evidence(value=add_aspect(header_name, add_aspect(": ", header_value_tainted))) loc = Location(path="foobar.py", line=35, spanId=123) v = Vulnerability(type=VULN_HEADER_INJECTION, evidence=ev, location=loc) - s = Source(origin="SomeOrigin", name="SomeName", value=header_value) - report = IastSpanReporter([s], {v}) + report = IastSpanReporter(vulnerabilities={v}) report.add_ranges_to_evidence_and_extract_sources(v) - redacted_report = HeaderInjection._redact_report(report) - for v in redacted_report.vulnerabilities: - assert v.evidence.valueParts == [{"value": header_name + ": "}, {"source": 0, "value": header_value}] + result = report.build_and_scrub_value_parts() + + assert result == { + "sources": [{"name": "SomeName", "origin": OriginType.PARAMETER, "value": header_value}], + "vulnerabilities": [ + { + "evidence": {"valueParts": [{"value": header_name + ": "}, {"source": 0, "value": header_value}]}, + "hash": ANY, + "location": {"line": ANY, "path": "foobar.py", "spanId": ANY}, + "type": VULN_HEADER_INJECTION, + } + ], + } @pytest.mark.parametrize( @@ -46,7 +54,7 @@ def test_header_injection_redact_excluded(header_name, header_value): ( "WWW-Authenticate", 'Basic realm="api"', - [{"value": "WWW-Authenticate: "}, {"source": 0, "value": 'Basic realm="api"'}], + [{"value": "WWW-Authenticate: "}, {"pattern": "abcdefghijklmnopq", "redacted": True, "source": 0}], ), ( "Authorization", @@ -63,20 +71,25 @@ def test_header_injection_redact_excluded(header_name, header_value): ], ) def test_common_django_header_injection_redact(header_name, header_value, value_part): - ev = Evidence( - valueParts=[ - {"value": header_name + ": "}, - {"value": header_value, "source": 0}, - ] - ) + header_value_tainted = taint_pyobject(pyobject=header_value, source_name="SomeName", source_value=header_value) + ev = Evidence(value=add_aspect(header_name, add_aspect(": ", header_value_tainted))) loc = Location(path="foobar.py", line=35, spanId=123) v = Vulnerability(type=VULN_HEADER_INJECTION, evidence=ev, location=loc) - s = Source(origin="SomeOrigin", name="SomeName", value=header_value) - report = IastSpanReporter([s], {v}) + report = IastSpanReporter(vulnerabilities={v}) report.add_ranges_to_evidence_and_extract_sources(v) - redacted_report = HeaderInjection._redact_report(report) - for v in redacted_report.vulnerabilities: - assert v.evidence.valueParts == value_part + result = report.build_and_scrub_value_parts() + + assert result == { + "sources": [{"name": "SomeName", "origin": OriginType.PARAMETER, "pattern": ANY, "redacted": True}], + "vulnerabilities": [ + { + "evidence": {"valueParts": value_part}, + "hash": ANY, + "location": {"line": ANY, "path": "foobar.py", "spanId": ANY}, + "type": VULN_HEADER_INJECTION, + } + ], + } @pytest.mark.parametrize( diff --git a/tests/appsec/iast/taint_sinks/test_path_traversal_redacted.py b/tests/appsec/iast/taint_sinks/test_path_traversal_redacted.py index ccd88c0ce11..aacaae0a156 100644 --- a/tests/appsec/iast/taint_sinks/test_path_traversal_redacted.py +++ b/tests/appsec/iast/taint_sinks/test_path_traversal_redacted.py @@ -1,14 +1,15 @@ import os +from mock.mock import ANY import pytest +from ddtrace.appsec._iast._taint_tracking import OriginType +from ddtrace.appsec._iast._taint_tracking import taint_pyobject from ddtrace.appsec._iast.constants import VULN_PATH_TRAVERSAL from ddtrace.appsec._iast.reporter import Evidence from ddtrace.appsec._iast.reporter import IastSpanReporter from ddtrace.appsec._iast.reporter import Location -from ddtrace.appsec._iast.reporter import Source from ddtrace.appsec._iast.reporter import Vulnerability -from ddtrace.appsec._iast.taint_sinks.path_traversal import PathTraversal ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -29,19 +30,25 @@ ], ) def test_path_traversal_redact_exclude(file_path): - ev = Evidence( - valueParts=[ - {"value": file_path, "source": 0}, - ] - ) + file_path = taint_pyobject(pyobject=file_path, source_name="path_traversal", source_value=file_path) + ev = Evidence(value=file_path) loc = Location(path="foobar.py", line=35, spanId=123) v = Vulnerability(type=VULN_PATH_TRAVERSAL, evidence=ev, location=loc) - s = Source(origin="SomeOrigin", name="SomeName", value="SomeValue") - report = IastSpanReporter([s], {v}) + report = IastSpanReporter(vulnerabilities={v}) + report.add_ranges_to_evidence_and_extract_sources(v) + result = report.build_and_scrub_value_parts() - redacted_report = PathTraversal._redact_report(report) - for v in redacted_report.vulnerabilities: - assert v.evidence.valueParts == [{"source": 0, "value": file_path}] + assert result == { + "sources": [{"name": "path_traversal", "origin": OriginType.PARAMETER, "value": file_path}], + "vulnerabilities": [ + { + "evidence": {"valueParts": [{"source": 0, "value": file_path}]}, + "hash": ANY, + "location": {"line": ANY, "path": "foobar.py", "spanId": ANY}, + "type": VULN_PATH_TRAVERSAL, + } + ], + } @pytest.mark.parametrize( @@ -75,33 +82,45 @@ def test_path_traversal_redact_exclude(file_path): ], ) def test_path_traversal_redact_rel_paths(file_path): - ev = Evidence( - valueParts=[ - {"value": file_path, "source": 0}, - ] - ) + file_path = taint_pyobject(pyobject=file_path, source_name="path_traversal", source_value=file_path) + ev = Evidence(value=file_path) loc = Location(path="foobar.py", line=35, spanId=123) v = Vulnerability(type=VULN_PATH_TRAVERSAL, evidence=ev, location=loc) - s = Source(origin="SomeOrigin", name="SomeName", value="SomeValue") - report = IastSpanReporter([s], {v}) + report = IastSpanReporter(vulnerabilities={v}) + report.add_ranges_to_evidence_and_extract_sources(v) + result = report.build_and_scrub_value_parts() - redacted_report = PathTraversal._redact_report(report) - for v in redacted_report.vulnerabilities: - assert v.evidence.valueParts == [{"source": 0, "value": file_path}] + assert result == { + "sources": [{"name": "path_traversal", "origin": OriginType.PARAMETER, "value": file_path}], + "vulnerabilities": [ + { + "evidence": {"valueParts": [{"source": 0, "value": file_path}]}, + "hash": ANY, + "location": {"line": ANY, "path": "foobar.py", "spanId": ANY}, + "type": VULN_PATH_TRAVERSAL, + } + ], + } def test_path_traversal_redact_abs_paths(): file_path = os.path.join(ROOT_DIR, "../fixtures", "taint_sinks", "path_traversal_test_file.txt") - ev = Evidence( - valueParts=[ - {"value": file_path, "source": 0}, - ] - ) + file_path = taint_pyobject(pyobject=file_path, source_name="path_traversal", source_value=file_path) + ev = Evidence(value=file_path) loc = Location(path="foobar.py", line=35, spanId=123) v = Vulnerability(type=VULN_PATH_TRAVERSAL, evidence=ev, location=loc) - s = Source(origin="SomeOrigin", name="SomeName", value="SomeValue") - report = IastSpanReporter([s], {v}) + report = IastSpanReporter(vulnerabilities={v}) + report.add_ranges_to_evidence_and_extract_sources(v) + result = report.build_and_scrub_value_parts() - redacted_report = PathTraversal._redact_report(report) - for v in redacted_report.vulnerabilities: - assert v.evidence.valueParts == [{"source": 0, "value": file_path}] + assert result == { + "sources": [{"name": "path_traversal", "origin": OriginType.PARAMETER, "value": file_path}], + "vulnerabilities": [ + { + "evidence": {"valueParts": [{"source": 0, "value": file_path}]}, + "hash": ANY, + "location": {"line": ANY, "path": "foobar.py", "spanId": ANY}, + "type": VULN_PATH_TRAVERSAL, + } + ], + } diff --git a/tests/appsec/iast/taint_sinks/test_sql_injection.py b/tests/appsec/iast/taint_sinks/test_sql_injection.py index 54efea82ffe..85f0e8e123e 100644 --- a/tests/appsec/iast/taint_sinks/test_sql_injection.py +++ b/tests/appsec/iast/taint_sinks/test_sql_injection.py @@ -41,26 +41,25 @@ def test_sql_injection(fixture_path, fixture_module, iast_span_defaults): mod.sqli_simple(table) span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) assert span_report - - assert len(span_report.vulnerabilities) == 1 - vulnerability = list(span_report.vulnerabilities)[0] - source = span_report.sources[0] - assert vulnerability.type == VULN_SQL_INJECTION - assert vulnerability.evidence.valueParts == [ + data = span_report.build_and_scrub_value_parts() + vulnerability = data["vulnerabilities"][0] + source = data["sources"][0] + assert vulnerability["type"] == VULN_SQL_INJECTION + assert vulnerability["evidence"]["valueParts"] == [ {"value": "SELECT "}, {"redacted": True}, {"value": " FROM "}, {"value": "students", "source": 0}, ] - assert vulnerability.evidence.value is None - assert source.name == "test_ossystem" - assert source.origin == OriginType.PARAMETER - assert source.value == "students" + assert "value" not in vulnerability["evidence"].keys() + assert source["name"] == "test_ossystem" + assert source["origin"] == OriginType.PARAMETER + assert source["value"] == "students" line, hash_value = get_line_and_hash("test_sql_injection", VULN_SQL_INJECTION, filename=fixture_path) - assert vulnerability.location.line == line - assert vulnerability.location.path == fixture_path - assert vulnerability.hash == hash_value + assert vulnerability["location"]["path"] == fixture_path + assert vulnerability["location"]["line"] == line + assert vulnerability["hash"] == hash_value @pytest.mark.parametrize("fixture_path,fixture_module", DDBBS) @@ -80,6 +79,6 @@ def test_sql_injection_deduplication(fixture_path, fixture_module, iast_span_ded span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_deduplication_enabled) assert span_report - - assert len(span_report.vulnerabilities) == 1 + data = span_report.build_and_scrub_value_parts() + assert len(data["vulnerabilities"]) == 1 VulnerabilityBase._prepare_report._reset_cache() diff --git a/tests/appsec/iast/taint_sinks/test_sql_injection_redacted.py b/tests/appsec/iast/taint_sinks/test_sql_injection_redacted.py index 4122b53d402..a4d1da049f8 100644 --- a/tests/appsec/iast/taint_sinks/test_sql_injection_redacted.py +++ b/tests/appsec/iast/taint_sinks/test_sql_injection_redacted.py @@ -1,13 +1,16 @@ import pytest from ddtrace.appsec._constants import IAST +from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted +from ddtrace.appsec._iast._taint_tracking import origin_to_str from ddtrace.appsec._iast._taint_tracking import str_to_origin +from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect from ddtrace.appsec._iast.constants import VULN_SQL_INJECTION from ddtrace.appsec._iast.reporter import Evidence from ddtrace.appsec._iast.reporter import IastSpanReporter from ddtrace.appsec._iast.reporter import Location -from ddtrace.appsec._iast.reporter import Source from ddtrace.appsec._iast.reporter import Vulnerability from ddtrace.appsec._iast.taint_sinks.sql_injection import SqlInjection from ddtrace.internal import core @@ -18,33 +21,7 @@ # FIXME: ideally all these should pass, through the key is that we don't leak any potential PII -_ignore_list = { - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, # unsupported weird strings - 23, - 28, - 31, - 33, - 34, # difference in numerics parsing (e.g. sign in the previous valuepart) - 40, - 41, - 42, - 43, - 44, # overlapping ":string", not supported by sqlparser, - 45, - 46, - 47, - 49, - 50, - 51, - 52, # slight differences in sqlparser parsing -} +_ignore_list = {46, 47} @pytest.mark.parametrize( @@ -74,164 +51,212 @@ def test_sqli_redaction_suite(evidence_input, sources_expected, vulnerabilities_ span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) assert span_report - vulnerability = list(span_report.vulnerabilities)[0] + span_report.build_and_scrub_value_parts() + result = span_report._to_dict() + vulnerability = list(result["vulnerabilities"])[0] + source = list(result["sources"])[0] + source["origin"] = origin_to_str(source["origin"]) - assert vulnerability.type == VULN_SQL_INJECTION - assert vulnerability.evidence.valueParts == vulnerabilities_expected["evidence"]["valueParts"] + assert vulnerability["type"] == VULN_SQL_INJECTION + assert source == sources_expected -@pytest.mark.skip(reason="TODO: Currently replacing too eagerly here") def test_redacted_report_no_match(): - ev = Evidence(value="SomeEvidenceValue") - orig_ev = ev.value + string_evicence = taint_pyobject( + pyobject="SomeEvidenceValue", source_name="source_name", source_value="SomeEvidenceValue" + ) + ev = Evidence(value=string_evicence) loc = Location(path="foobar.py", line=35, spanId=123) v = Vulnerability(type=VULN_SQL_INJECTION, evidence=ev, location=loc) - s = Source(origin="SomeOrigin", name="SomeName", value="SomeValue") - report = IastSpanReporter([s], {v}) + report = IastSpanReporter(vulnerabilities={v}) + report.add_ranges_to_evidence_and_extract_sources(v) + result = report.build_and_scrub_value_parts() + + assert result["vulnerabilities"] + + for v in result["vulnerabilities"]: + assert v["evidence"] == {"valueParts": [{"source": 0, "value": "SomeEvidenceValue"}]} - redacted_report = SqlInjection._redact_report(report) - for v in redacted_report.vulnerabilities: - assert not v.evidence.redacted - assert v.evidence.value == orig_ev + for v in result["sources"]: + assert v == {"name": "source_name", "origin": OriginType.PARAMETER, "value": "SomeEvidenceValue"} def test_redacted_report_source_name_match(): - ev = Evidence(value="'SomeEvidenceValue'") + string_evicence = taint_pyobject(pyobject="'SomeEvidenceValue'", source_name="secret", source_value="SomeValue") + ev = Evidence(value=string_evicence) loc = Location(path="foobar.py", line=35, spanId=123) v = Vulnerability(type=VULN_SQL_INJECTION, evidence=ev, location=loc) - s = Source(origin="SomeOrigin", name="secret", value="SomeValue") - report = IastSpanReporter([s], {v}) + report = IastSpanReporter(vulnerabilities={v}) + report.add_ranges_to_evidence_and_extract_sources(v) + result = report.build_and_scrub_value_parts() - redacted_report = SqlInjection._redact_report(report) - for v in redacted_report.vulnerabilities: - assert not v.evidence.value + assert result["vulnerabilities"] + + for v in result["vulnerabilities"]: + assert v["evidence"] == {"valueParts": [{"pattern": "*******************", "redacted": True, "source": 0}]} + + for v in result["sources"]: + assert v == {"name": "secret", "origin": OriginType.PARAMETER, "pattern": "abcdefghi", "redacted": True} def test_redacted_report_source_value_match(): - ev = Evidence(value="'SomeEvidenceValue'") + string_evicence = taint_pyobject( + pyobject="'SomeEvidenceValue'", source_name="SomeName", source_value="somepassword" + ) + ev = Evidence(value=string_evicence) loc = Location(path="foobar.py", line=35, spanId=123) v = Vulnerability(type=VULN_SQL_INJECTION, evidence=ev, location=loc) - s = Source(origin="SomeOrigin", name="SomeName", value="somepassword") - report = IastSpanReporter([s], {v}) + report = IastSpanReporter(vulnerabilities={v}) + report.add_ranges_to_evidence_and_extract_sources(v) + result = report.build_and_scrub_value_parts() - redacted_report = SqlInjection._redact_report(report) - for v in redacted_report.vulnerabilities: - assert not v.evidence.value + assert result["vulnerabilities"] + + for v in result["vulnerabilities"]: + assert v["evidence"] == {"valueParts": [{"pattern": "*******************", "redacted": True, "source": 0}]} + + for v in result["sources"]: + assert v == {"name": "SomeName", "origin": OriginType.PARAMETER, "pattern": "abcdefghijkl", "redacted": True} def test_redacted_report_evidence_value_match_also_redacts_source_value(): - ev = Evidence(value="'SomeSecretPassword'") + string_evicence = taint_pyobject( + pyobject="'SomeSecretPassword'", source_name="SomeName", source_value="SomeSecretPassword" + ) + ev = Evidence(value=string_evicence) loc = Location(path="foobar.py", line=35, spanId=123) v = Vulnerability(type=VULN_SQL_INJECTION, evidence=ev, location=loc) - s = Source(origin="SomeOrigin", name="SomeName", value="SomeSecretPassword") - report = IastSpanReporter([s], {v}) + report = IastSpanReporter(vulnerabilities={v}) + report.add_ranges_to_evidence_and_extract_sources(v) + result = report.build_and_scrub_value_parts() - redacted_report = SqlInjection._redact_report(report) - for v in redacted_report.vulnerabilities: - assert not v.evidence.value - for s in redacted_report.sources: - assert s.redacted - assert s.pattern == "abcdefghijklmnopqr" - assert not s.value + assert result["vulnerabilities"] + + for v in result["vulnerabilities"]: + assert v["evidence"] == {"valueParts": [{"pattern": "********************", "redacted": True, "source": 0}]} + + for v in result["sources"]: + assert v == { + "name": "SomeName", + "origin": OriginType.PARAMETER, + "pattern": "abcdefghijklmnopqr", + "redacted": True, + } def test_redacted_report_valueparts(): - ev = Evidence( - valueParts=[ - {"value": "SELECT * FROM users WHERE password = '"}, - {"value": "1234", "source": 0}, - {"value": ":{SHA1}'"}, - ] - ) + string_evicence = taint_pyobject(pyobject="1234", source_name="SomeName", source_value="SomeValue") + + ev = Evidence(value=add_aspect("SELECT * FROM users WHERE password = '", add_aspect(string_evicence, ":{SHA1}'"))) loc = Location(path="foobar.py", line=35, spanId=123) v = Vulnerability(type=VULN_SQL_INJECTION, evidence=ev, location=loc) - s = Source(origin="SomeOrigin", name="SomeName", value="SomeValue") - report = IastSpanReporter([s], {v}) + report = IastSpanReporter(vulnerabilities={v}) + report.add_ranges_to_evidence_and_extract_sources(v) + result = report.build_and_scrub_value_parts() + + assert result["vulnerabilities"] + + for v in result["vulnerabilities"]: + assert v["evidence"] == { + "valueParts": [ + {"value": "SELECT * FROM users WHERE password = '"}, + {"pattern": "****", "redacted": True, "source": 0}, + {"redacted": True}, + {"value": "'"}, + ] + } - redacted_report = SqlInjection._redact_report(report) - for v in redacted_report.vulnerabilities: - assert v.evidence.valueParts == [ - {"value": "SELECT * FROM users WHERE password = '"}, - {"redacted": True}, - {"value": ":{SHA1}'"}, - ] + for v in result["sources"]: + assert v == {"name": "SomeName", "origin": OriginType.PARAMETER, "pattern": "abcdefghi", "redacted": True} def test_redacted_report_valueparts_username_not_tainted(): - ev = Evidence( - valueParts=[ - {"value": "SELECT * FROM users WHERE username = '"}, - {"value": "pepito"}, - {"value": "' AND password = '"}, - {"value": "secret", "source": 0}, - {"value": "'"}, - ] + string_evicence = taint_pyobject(pyobject="secret", source_name="SomeName", source_value="SomeValue") + + string_tainted = add_aspect( + "SELECT * FROM users WHERE username = '", + add_aspect("pepito", add_aspect("' AND password = '", add_aspect(string_evicence, "'"))), ) + ev = Evidence(value=string_tainted, dialect="POSTGRES") loc = Location(path="foobar.py", line=35, spanId=123) v = Vulnerability(type=VULN_SQL_INJECTION, evidence=ev, location=loc) - s = Source(origin="SomeOrigin", name="SomeName", value="SomeValue") - report = IastSpanReporter([s], {v}) - - redacted_report = SqlInjection._redact_report(report) - for v in redacted_report.vulnerabilities: - assert v.evidence.valueParts == [ - {"value": "SELECT * FROM users WHERE username = '"}, - {"redacted": True}, - {"value": "'"}, - {"value": " AND password = "}, - {"value": "'"}, - {"redacted": True}, - {"value": "'"}, - ] + report = IastSpanReporter(vulnerabilities={v}) + report.add_ranges_to_evidence_and_extract_sources(v) + result = report.build_and_scrub_value_parts() + + assert result["vulnerabilities"] + + for v in result["vulnerabilities"]: + assert v["evidence"] == { + "valueParts": [ + {"value": "SELECT * FROM users WHERE username = '"}, + {"redacted": True}, + {"value": "' AND password = '"}, + {"pattern": "******", "redacted": True, "source": 0}, + {"value": "'"}, + ] + } + + for v in result["sources"]: + assert v == {"name": "SomeName", "origin": OriginType.PARAMETER, "pattern": "abcdefghi", "redacted": True} def test_redacted_report_valueparts_username_tainted(): - ev = Evidence( - valueParts=[ - {"value": "SELECT * FROM users WHERE username = '"}, - {"value": "pepito", "source": 0}, - {"value": "' AND password = '"}, - {"value": "secret", "source": 0}, - {"value": "'"}, - ] + string_evicence = taint_pyobject(pyobject="secret", source_name="SomeName", source_value="SomeValue") + + string_tainted = add_aspect( + "SELECT * FROM users WHERE username = '", + add_aspect(string_evicence, add_aspect("' AND password = '", add_aspect(string_evicence, "'"))), ) + ev = Evidence(value=string_tainted, dialect="POSTGRES") loc = Location(path="foobar.py", line=35, spanId=123) v = Vulnerability(type=VULN_SQL_INJECTION, evidence=ev, location=loc) - s = Source(origin="SomeOrigin", name="SomeName", value="SomeValue") - report = IastSpanReporter([s], {v}) - - redacted_report = SqlInjection._redact_report(report) - for v in redacted_report.vulnerabilities: - assert v.evidence.valueParts == [ - {"value": "SELECT * FROM users WHERE username = '"}, - {"redacted": True}, - {"value": "'"}, - {"value": " AND password = "}, - {"value": "'"}, - {"redacted": True}, - {"value": "'"}, - ] + report = IastSpanReporter(vulnerabilities={v}) + report.add_ranges_to_evidence_and_extract_sources(v) + result = report.build_and_scrub_value_parts() + + assert result["vulnerabilities"] + + for v in result["vulnerabilities"]: + assert v["evidence"] == { + "valueParts": [ + {"value": "SELECT * FROM users WHERE username = '"}, + {"pattern": "******", "redacted": True, "source": 0}, + {"value": "' AND password = '"}, + {"pattern": "******", "redacted": True, "source": 0}, + {"value": "'"}, + ] + } + + for v in result["sources"]: + assert v == {"name": "SomeName", "origin": OriginType.PARAMETER, "pattern": "abcdefghi", "redacted": True} def test_regression_ci_failure(): - ev = Evidence( - valueParts=[ - {"value": "SELECT tbl_name FROM sqlite_"}, - {"value": "master", "source": 0}, - {"value": "WHERE tbl_name LIKE 'password'"}, - ] + string_evicence = taint_pyobject(pyobject="master", source_name="SomeName", source_value="master") + + string_tainted = add_aspect( + "SELECT tbl_name FROM sqlite_", add_aspect(string_evicence, "WHERE tbl_name LIKE 'password'") ) + ev = Evidence(value=string_tainted, dialect="POSTGRES") loc = Location(path="foobar.py", line=35, spanId=123) v = Vulnerability(type=VULN_SQL_INJECTION, evidence=ev, location=loc) - s = Source(origin="SomeOrigin", name="SomeName", value="SomeValue") - report = IastSpanReporter([s], {v}) - - redacted_report = SqlInjection._redact_report(report) - for v in redacted_report.vulnerabilities: - assert v.evidence.valueParts == [ - {"value": "SELECT tbl_name FROM sqlite_"}, - {"source": 0, "value": "master"}, - {"value": "WHERE tbl_name LIKE '"}, - {"redacted": True}, - {"value": "'"}, - ] + report = IastSpanReporter(vulnerabilities={v}) + report.add_ranges_to_evidence_and_extract_sources(v) + result = report.build_and_scrub_value_parts() + + assert result["vulnerabilities"] + + for v in result["vulnerabilities"]: + assert v["evidence"] == { + "valueParts": [ + {"value": "SELECT tbl_name FROM sqlite_"}, + {"source": 0, "value": "master"}, + {"value": "WHERE tbl_name LIKE '"}, + {"redacted": True}, + {"value": "'"}, + ] + } + + for v in result["sources"]: + assert v == {"name": "SomeName", "origin": OriginType.PARAMETER, "value": "master"} diff --git a/tests/appsec/iast/test_taint_utils.py b/tests/appsec/iast/test_taint_utils.py index 9ccf6df4507..a60cc2c547a 100644 --- a/tests/appsec/iast/test_taint_utils.py +++ b/tests/appsec/iast/test_taint_utils.py @@ -9,7 +9,7 @@ from ddtrace.appsec._iast._taint_tracking import taint_pyobject from ddtrace.appsec._iast._taint_utils import LazyTaintDict from ddtrace.appsec._iast._taint_utils import LazyTaintList -from ddtrace.appsec._iast._taint_utils import check_tainted_args +from ddtrace.appsec._iast._taint_utils import check_tainted_dbapi_args def setup(): @@ -192,17 +192,17 @@ def test_checked_tainted_args(): untainted_arg = "gallahad the pure" # Returns False: Untainted first argument - assert not check_tainted_args( + assert not check_tainted_dbapi_args( args=(untainted_arg,), kwargs=None, tracer=None, integration_name="sqlite", method=cursor.execute ) # Returns False: Untainted first argument - assert not check_tainted_args( + assert not check_tainted_dbapi_args( args=(untainted_arg, tainted_arg), kwargs=None, tracer=None, integration_name="sqlite", method=cursor.execute ) # Returns False: Integration name not in list - assert not check_tainted_args( + assert not check_tainted_dbapi_args( args=(tainted_arg,), kwargs=None, tracer=None, @@ -211,7 +211,7 @@ def test_checked_tainted_args(): ) # Returns False: Wrong function name - assert not check_tainted_args( + assert not check_tainted_dbapi_args( args=(tainted_arg,), kwargs=None, tracer=None, @@ -220,17 +220,17 @@ def test_checked_tainted_args(): ) # Returns True: - assert check_tainted_args( + assert check_tainted_dbapi_args( args=(tainted_arg, untainted_arg), kwargs=None, tracer=None, integration_name="sqlite", method=cursor.execute ) # Returns True: - assert check_tainted_args( + assert check_tainted_dbapi_args( args=(tainted_arg, untainted_arg), kwargs=None, tracer=None, integration_name="mysql", method=cursor.execute ) # Returns True: - assert check_tainted_args( + assert check_tainted_dbapi_args( args=(tainted_arg, untainted_arg), kwargs=None, tracer=None, integration_name="psycopg", method=cursor.execute ) diff --git a/tests/contrib/dbapi/test_dbapi_appsec.py b/tests/contrib/dbapi/test_dbapi_appsec.py index 819f969ede6..3ba165da8cf 100644 --- a/tests/contrib/dbapi/test_dbapi_appsec.py +++ b/tests/contrib/dbapi/test_dbapi_appsec.py @@ -36,7 +36,7 @@ def test_tainted_query(self): traced_cursor.execute(query) cursor.execute.assert_called_once_with(query) - mock_sql_injection_report.assert_called_once_with(evidence_value=query) + mock_sql_injection_report.assert_called_once_with(evidence_value=query, dialect="sqlite") @pytest.mark.skipif(not _is_python_version_supported(), reason="IAST compatible versions") def test_tainted_query_args(self): diff --git a/tests/contrib/django/django_app/appsec_urls.py b/tests/contrib/django/django_app/appsec_urls.py index c076b238a76..ffdbd413936 100644 --- a/tests/contrib/django/django_app/appsec_urls.py +++ b/tests/contrib/django/django_app/appsec_urls.py @@ -99,10 +99,10 @@ def sqli_http_request_header_name(request): def sqli_http_request_header_value(request): value = [x for x in request.META.values() if x == "master"][0] - with connection.cursor() as cursor: + query = add_aspect("SELECT 1 FROM sqlite_", value) # label iast_enabled_sqli_http_request_header_value - cursor.execute(add_aspect("SELECT 1 FROM sqlite_", value)) + cursor.execute(query) return HttpResponse(request.META["HTTP_USER_AGENT"], status=200) diff --git a/tests/contrib/django/test_django_appsec_iast.py b/tests/contrib/django/test_django_appsec_iast.py index 7298e06cd22..f3716c2f799 100644 --- a/tests/contrib/django/test_django_appsec_iast.py +++ b/tests/contrib/django/test_django_appsec_iast.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- import json -import mock import pytest from ddtrace.appsec._constants import IAST @@ -27,9 +26,9 @@ def reset_context(): from ddtrace.appsec._iast._taint_tracking import create_context from ddtrace.appsec._iast._taint_tracking import reset_context - yield - reset_context() - _ = create_context() + yield + reset_context() + _ = create_context() def _aux_appsec_get_root_span( @@ -65,7 +64,7 @@ def _aux_appsec_get_root_span( @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_django_weak_hash(client, test_spans, tracer): - with override_global_config(dict(_asm_enabled=True, _iast_enabled=True)), override_env({"DD_IAST_ENABLED": "True"}): + with override_global_config(dict(_asm_enabled=True, _iast_enabled=True, _deduplication_enabled=False)): oce.reconfigure() patch_iast({"weak_hash": True}) root_span, _ = _aux_appsec_get_root_span(client, test_spans, tracer, url="/appsec/weak-hash/") @@ -78,10 +77,7 @@ def test_django_weak_hash(client, test_spans, tracer): @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_django_tainted_user_agent_iast_enabled(client, test_spans, tracer): - with override_global_config(dict(_iast_enabled=True)), override_env({"DD_IAST_ENABLED": "True"}): - oce.reconfigure() - tracer._iast_enabled = True - + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)): root_span, response = _aux_appsec_get_root_span( client, test_spans, @@ -98,7 +94,7 @@ def test_django_tainted_user_agent_iast_enabled(client, test_spans, tracer): @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_django_tainted_user_agent_iast_disabled(client, test_spans, tracer): - with override_global_config(dict(_iast_enabled=False)), override_env({"DD_IAST_ENABLED": "True"}): + with override_global_config(dict(_iast_enabled=False, _deduplication_enabled=False)): oce.reconfigure() root_span, response = _aux_appsec_get_root_span( @@ -120,9 +116,7 @@ def test_django_tainted_user_agent_iast_disabled(client, test_spans, tracer): @pytest.mark.django_db() @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_django_tainted_user_agent_iast_enabled_sqli_http_request_parameter(client, test_spans, tracer): - with override_global_config(dict(_iast_enabled=True)), mock.patch( - "ddtrace.contrib.dbapi._is_iast_enabled", return_value=True - ), override_env({"DD_IAST_ENABLED": "True"}): + with override_global_config(dict(_iast_enabled=True)): root_span, response = _aux_appsec_get_root_span( client, test_spans, @@ -143,14 +137,20 @@ def test_django_tainted_user_agent_iast_enabled_sqli_http_request_parameter(clie line, hash_value = get_line_and_hash("iast_enabled_sqli_http_request_parameter", vuln_type, filename=TEST_FILE) assert loaded["sources"] == [ - {"origin": "http.request.parameter", "name": "q", "value": "SELECT 1 FROM sqlite_master"} + { + "name": "q", + "origin": "http.request.parameter", + "pattern": "abcdefghijklmnopqrstuvwxyzA", + "redacted": True, + } ] + assert loaded["vulnerabilities"][0]["type"] == vuln_type assert loaded["vulnerabilities"][0]["evidence"] == { "valueParts": [ - {"value": "SELECT ", "source": 0}, - {"redacted": True}, - {"value": " FROM sqlite_master", "source": 0}, + {"source": 0, "value": "SELECT "}, + {"pattern": "h", "redacted": True, "source": 0}, + {"source": 0, "value": " FROM sqlite_master"}, ] } assert loaded["vulnerabilities"][0]["location"]["path"] == TEST_FILE @@ -161,9 +161,7 @@ def test_django_tainted_user_agent_iast_enabled_sqli_http_request_parameter(clie @pytest.mark.django_db() @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_django_tainted_user_agent_iast_enabled_sqli_http_request_header_value(client, test_spans, tracer): - with override_global_config(dict(_iast_enabled=True)), mock.patch( - "ddtrace.contrib.dbapi._is_iast_enabled", return_value=True - ), override_env({"DD_IAST_ENABLED": "True"}): + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)): root_span, response = _aux_appsec_get_root_span( client, test_spans, @@ -174,37 +172,34 @@ def test_django_tainted_user_agent_iast_enabled_sqli_http_request_header_value(c headers={"HTTP_USER_AGENT": "master"}, ) - vuln_type = "SQL_INJECTION" - loaded = json.loads(root_span.get_tag(IAST.JSON)) + assert response.status_code == 200 + assert response.content == b"master" - line, hash_value = get_line_and_hash( - "iast_enabled_sqli_http_request_header_value", vuln_type, filename=TEST_FILE - ) + loaded = json.loads(root_span.get_tag(IAST.JSON)) assert loaded["sources"] == [{"origin": "http.request.header", "name": "HTTP_USER_AGENT", "value": "master"}] - assert loaded["vulnerabilities"][0]["type"] == vuln_type - assert loaded["vulnerabilities"][0]["hash"] == hash_value + assert loaded["vulnerabilities"][0]["type"] == VULN_SQL_INJECTION assert loaded["vulnerabilities"][0]["evidence"] == { "valueParts": [ {"value": "SELECT "}, {"redacted": True}, {"value": " FROM sqlite_"}, - {"value": "master", "source": 0}, + {"source": 0, "value": "master"}, ] } + + line, hash_value = get_line_and_hash( + "iast_enabled_sqli_http_request_header_value", VULN_SQL_INJECTION, filename=TEST_FILE + ) assert loaded["vulnerabilities"][0]["location"]["path"] == TEST_FILE assert loaded["vulnerabilities"][0]["location"]["line"] == line - - assert response.status_code == 200 - assert response.content == b"master" + assert loaded["vulnerabilities"][0]["hash"] == hash_value @pytest.mark.django_db() @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_django_tainted_user_agent_iast_disabled_sqli_http_request_header_value(client, test_spans, tracer): - with override_global_config(dict(_iast_enabled=False)), mock.patch( - "ddtrace.contrib.dbapi._is_iast_enabled", return_value=False - ), override_env({"DD_IAST_ENABLED": "True"}): + with override_global_config(dict(_iast_enabled=False)): root_span, response = _aux_appsec_get_root_span( client, test_spans, @@ -224,9 +219,7 @@ def test_django_tainted_user_agent_iast_disabled_sqli_http_request_header_value( @pytest.mark.django_db() @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_django_tainted_user_agent_iast_enabled_sqli_http_request_header_name(client, test_spans, tracer): - with override_global_config(dict(_iast_enabled=True)), mock.patch( - "ddtrace.contrib.dbapi._is_iast_enabled", return_value=True - ), override_env({"DD_IAST_ENABLED": "True"}): + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)): root_span, response = _aux_appsec_get_root_span( client, test_spans, @@ -237,17 +230,13 @@ def test_django_tainted_user_agent_iast_enabled_sqli_http_request_header_name(cl headers={"master": "test/1.2.3"}, ) - vuln_type = "SQL_INJECTION" + assert response.status_code == 200 + assert response.content == b"test/1.2.3" loaded = json.loads(root_span.get_tag(IAST.JSON)) - line, hash_value = get_line_and_hash( - "iast_enabled_sqli_http_request_header_name", vuln_type, filename=TEST_FILE - ) - assert loaded["sources"] == [{"origin": "http.request.header.name", "name": "master", "value": "master"}] - assert loaded["vulnerabilities"][0]["type"] == vuln_type - assert loaded["vulnerabilities"][0]["hash"] == hash_value + assert loaded["vulnerabilities"][0]["type"] == VULN_SQL_INJECTION assert loaded["vulnerabilities"][0]["evidence"] == { "valueParts": [ {"value": "SELECT "}, @@ -256,19 +245,19 @@ def test_django_tainted_user_agent_iast_enabled_sqli_http_request_header_name(cl {"value": "master", "source": 0}, ] } + + line, hash_value = get_line_and_hash( + "iast_enabled_sqli_http_request_header_name", VULN_SQL_INJECTION, filename=TEST_FILE + ) assert loaded["vulnerabilities"][0]["location"]["path"] == TEST_FILE assert loaded["vulnerabilities"][0]["location"]["line"] == line - - assert response.status_code == 200 - assert response.content == b"test/1.2.3" + assert loaded["vulnerabilities"][0]["hash"] == hash_value @pytest.mark.django_db() @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_django_tainted_user_agent_iast_disabled_sqli_http_request_header_name(client, test_spans, tracer): - with override_global_config(dict(_iast_enabled=False)), mock.patch( - "ddtrace.contrib.dbapi._is_iast_enabled", return_value=True - ), override_env({"DD_IAST_ENABLED": "True"}): + with override_global_config(dict(_iast_enabled=False)): root_span, response = _aux_appsec_get_root_span( client, test_spans, @@ -288,9 +277,7 @@ def test_django_tainted_user_agent_iast_disabled_sqli_http_request_header_name(c @pytest.mark.django_db() @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_django_iast_enabled_full_sqli_http_path_parameter(client, test_spans, tracer): - with override_global_config(dict(_iast_enabled=True)), mock.patch( - "ddtrace.contrib.dbapi._is_iast_enabled", return_value=True - ), override_env({"DD_IAST_ENABLED": "True"}): + with override_global_config(dict(_iast_enabled=True)): root_span, response = _aux_appsec_get_root_span( client, test_spans, @@ -298,19 +285,15 @@ def test_django_iast_enabled_full_sqli_http_path_parameter(client, test_spans, t url="/appsec/sqli_http_path_parameter/sqlite_master/", headers={"HTTP_USER_AGENT": "test/1.2.3"}, ) - vuln_type = "SQL_INJECTION" + assert response.status_code == 200 + assert response.content == b"test/1.2.3" loaded = json.loads(root_span.get_tag(IAST.JSON)) - line, hash_value = get_line_and_hash( - "iast_enabled_full_sqli_http_path_parameter", vuln_type, filename=TEST_FILE - ) - assert loaded["sources"] == [ {"origin": "http.request.path.parameter", "name": "q_http_path_parameter", "value": "sqlite_master"} ] - assert loaded["vulnerabilities"][0]["type"] == vuln_type - assert loaded["vulnerabilities"][0]["hash"] == hash_value + assert loaded["vulnerabilities"][0]["type"] == VULN_SQL_INJECTION assert loaded["vulnerabilities"][0]["evidence"] == { "valueParts": [ {"value": "SELECT "}, @@ -319,19 +302,18 @@ def test_django_iast_enabled_full_sqli_http_path_parameter(client, test_spans, t {"value": "sqlite_master", "source": 0}, ] } + line, hash_value = get_line_and_hash( + "iast_enabled_full_sqli_http_path_parameter", VULN_SQL_INJECTION, filename=TEST_FILE + ) assert loaded["vulnerabilities"][0]["location"]["path"] == TEST_FILE assert loaded["vulnerabilities"][0]["location"]["line"] == line - - assert response.status_code == 200 - assert response.content == b"test/1.2.3" + assert loaded["vulnerabilities"][0]["hash"] == hash_value @pytest.mark.django_db() @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_django_iast_disabled_full_sqli_http_path_parameter(client, test_spans, tracer): - with override_global_config(dict(_iast_enabled=False)), mock.patch( - "ddtrace.contrib.dbapi._is_iast_enabled", return_value=False - ), override_env({"DD_IAST_ENABLED": "True"}): + with override_global_config(dict(_iast_enabled=False)): root_span, response = _aux_appsec_get_root_span( client, test_spans, @@ -349,9 +331,7 @@ def test_django_iast_disabled_full_sqli_http_path_parameter(client, test_spans, @pytest.mark.django_db() @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_django_tainted_user_agent_iast_enabled_sqli_http_cookies_name(client, test_spans, tracer): - with override_global_config(dict(_iast_enabled=True)), mock.patch( - "ddtrace.contrib.dbapi._is_iast_enabled", return_value=True - ), override_env({"DD_IAST_ENABLED": "True"}): + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)): root_span, response = _aux_appsec_get_root_span( client, test_spans, @@ -359,13 +339,11 @@ def test_django_tainted_user_agent_iast_enabled_sqli_http_cookies_name(client, t url="/appsec/sqli_http_request_cookie_name/", cookies={"master": "test/1.2.3"}, ) + assert response.status_code == 200 + assert response.content == b"test/1.2.3" loaded = json.loads(root_span.get_tag(IAST.JSON)) - line, hash_value = get_line_and_hash( - "iast_enabled_sqli_http_cookies_name", VULN_SQL_INJECTION, filename=TEST_FILE - ) - vulnerability = False for vuln in loaded["vulnerabilities"]: if vuln["type"] == VULN_SQL_INJECTION: @@ -374,7 +352,6 @@ def test_django_tainted_user_agent_iast_enabled_sqli_http_cookies_name(client, t assert vulnerability, "No {} reported".format(VULN_SQL_INJECTION) assert loaded["sources"] == [{"origin": "http.request.cookie.name", "name": "master", "value": "master"}] - assert vulnerability["hash"] == hash_value assert vulnerability["evidence"] == { "valueParts": [ {"value": "SELECT "}, @@ -383,19 +360,18 @@ def test_django_tainted_user_agent_iast_enabled_sqli_http_cookies_name(client, t {"value": "master", "source": 0}, ] } + line, hash_value = get_line_and_hash( + "iast_enabled_sqli_http_cookies_name", VULN_SQL_INJECTION, filename=TEST_FILE + ) assert vulnerability["location"]["path"] == TEST_FILE assert vulnerability["location"]["line"] == line - - assert response.status_code == 200 - assert response.content == b"test/1.2.3" + assert vulnerability["hash"] == hash_value @pytest.mark.django_db() @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_django_tainted_iast_disabled_sqli_http_cookies_name(client, test_spans, tracer): - with override_global_config(dict(_iast_enabled=False)), mock.patch( - "ddtrace.contrib.dbapi._is_iast_enabled", return_value=False - ), override_env({"DD_IAST_ENABLED": "True"}): + with override_global_config(dict(_iast_enabled=False)): root_span, response = _aux_appsec_get_root_span( client, test_spans, @@ -413,9 +389,7 @@ def test_django_tainted_iast_disabled_sqli_http_cookies_name(client, test_spans, @pytest.mark.django_db() @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_django_tainted_user_agent_iast_enabled_sqli_http_cookies_value(client, test_spans, tracer): - with override_global_config(dict(_iast_enabled=True)), mock.patch( - "ddtrace.contrib.dbapi._is_iast_enabled", return_value=True - ), override_env({"DD_IAST_ENABLED": "True"}): + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)): root_span, response = _aux_appsec_get_root_span( client, test_spans, @@ -423,10 +397,10 @@ def test_django_tainted_user_agent_iast_enabled_sqli_http_cookies_value(client, url="/appsec/sqli_http_request_cookie_value/", cookies={"master": "master"}, ) - vuln_type = "SQL_INJECTION" - loaded = json.loads(root_span.get_tag(IAST.JSON)) + assert response.status_code == 200 + assert response.content == b"master" - line, hash_value = get_line_and_hash("iast_enabled_sqli_http_cookies_value", vuln_type, filename=TEST_FILE) + loaded = json.loads(root_span.get_tag(IAST.JSON)) vulnerability = False for vuln in loaded["vulnerabilities"]: @@ -436,7 +410,7 @@ def test_django_tainted_user_agent_iast_enabled_sqli_http_cookies_value(client, assert vulnerability, "No {} reported".format(VULN_SQL_INJECTION) assert loaded["sources"] == [{"origin": "http.request.cookie.value", "name": "master", "value": "master"}] assert vulnerability["type"] == "SQL_INJECTION" - assert vulnerability["hash"] == hash_value + assert vulnerability["evidence"] == { "valueParts": [ {"value": "SELECT "}, @@ -445,19 +419,19 @@ def test_django_tainted_user_agent_iast_enabled_sqli_http_cookies_value(client, {"value": "master", "source": 0}, ] } + + line, hash_value = get_line_and_hash( + "iast_enabled_sqli_http_cookies_value", VULN_SQL_INJECTION, filename=TEST_FILE + ) assert vulnerability["location"]["line"] == line assert vulnerability["location"]["path"] == TEST_FILE - - assert response.status_code == 200 - assert response.content == b"master" + assert vulnerability["hash"] == hash_value @pytest.mark.django_db() @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_django_tainted_iast_disabled_sqli_http_cookies_value(client, test_spans, tracer): - with override_global_config(dict(_iast_enabled=False)), mock.patch( - "ddtrace.contrib.dbapi._is_iast_enabled", return_value=False - ), override_env({"DD_IAST_ENABLED": "True"}): + with override_global_config(dict(_iast_enabled=False)): root_span, response = _aux_appsec_get_root_span( client, test_spans, @@ -482,9 +456,7 @@ def test_django_tainted_iast_disabled_sqli_http_cookies_value(client, test_spans @pytest.mark.django_db() @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_django_tainted_user_agent_iast_enabled_sqli_http_body(client, test_spans, tracer, payload, content_type): - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)), override_env( - dict(DD_IAST_ENABLED="True") - ), mock.patch("ddtrace.contrib.dbapi._is_iast_enabled", return_value=True): + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)): root_span, response = _aux_appsec_get_root_span( client, test_spans, @@ -518,9 +490,7 @@ def test_django_tainted_user_agent_iast_enabled_sqli_http_body(client, test_span @pytest.mark.django_db() @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_django_tainted_iast_disabled_sqli_http_body(client, test_spans, tracer): - with override_global_config(dict(_iast_enabled=False)), mock.patch( - "ddtrace.contrib.dbapi._is_iast_enabled", return_value=False - ), override_env({"DD_IAST_ENABLED": "True"}): + with override_global_config(dict(_iast_enabled=False)): root_span, response = _aux_appsec_get_root_span( client, test_spans, @@ -538,9 +508,7 @@ def test_django_tainted_iast_disabled_sqli_http_body(client, test_spans, tracer) @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_querydict_django_with_iast(client, test_spans, tracer): - with override_global_config(dict(_iast_enabled=True)), mock.patch( - "ddtrace.contrib.dbapi._is_iast_enabled", return_value=False - ), override_env({"DD_IAST_ENABLED": "True"}): + with override_global_config(dict(_iast_enabled=True)): root_span, response = _aux_appsec_get_root_span( client, test_spans, @@ -558,9 +526,7 @@ def test_querydict_django_with_iast(client, test_spans, tracer): @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_django_command_injection(client, test_spans, tracer): - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)), override_env( - dict(DD_IAST_ENABLED="True") - ): + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)): oce.reconfigure() patch_iast({"command_injection": True}) root_span, _ = _aux_appsec_get_root_span( @@ -590,9 +556,7 @@ def test_django_command_injection(client, test_spans, tracer): @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_django_header_injection(client, test_spans, tracer): - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)), override_env( - dict(DD_IAST_ENABLED="True") - ): + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)): oce.reconfigure() patch_iast({"header_injection": True}) root_span, _ = _aux_appsec_get_root_span( diff --git a/tests/contrib/flask/test_flask_appsec_iast.py b/tests/contrib/flask/test_flask_appsec_iast.py index d3b7f603ab0..702375dcd8c 100644 --- a/tests/contrib/flask/test_flask_appsec_iast.py +++ b/tests/contrib/flask/test_flask_appsec_iast.py @@ -44,7 +44,6 @@ def setUp(self): with override_global_config( dict( _iast_enabled=True, - _asm_enabled=True, _deduplication_enabled=False, ) ), override_env(IAST_ENV): @@ -77,7 +76,7 @@ def sqli_1(param_str): with override_global_config( dict( _iast_enabled=True, - _asm_enabled=True, + _deduplication_enabled=False, ) ): resp = self.client.post("/sqli/sqlite_master/", data={"name": "test"}) @@ -128,7 +127,7 @@ def sqli_2(param_str): with override_global_config( dict( _iast_enabled=True, - _asm_enabled=True, + _deduplication_enabled=False, ) ): resp = self.client.post( @@ -377,7 +376,6 @@ def sqli_7(): with override_global_config( dict( _iast_enabled=True, - _asm_enabled=True, _deduplication_enabled=False, ) ), override_env(IAST_ENV): @@ -444,7 +442,7 @@ def sqli_8(): with override_global_config( dict( _iast_enabled=True, - _asm_enabled=True, + _deduplication_enabled=False, ) ): if tuple(map(int, werkzeug_version.split("."))) >= (2, 3): @@ -505,6 +503,7 @@ def sqli_9(): with override_global_config( dict( _iast_enabled=True, + _deduplication_enabled=False, ) ): resp = self.client.get("/sqli/parameter/?table=sqlite_master") @@ -610,7 +609,7 @@ def header_injection(): with override_global_config( dict( _iast_enabled=True, - _asm_enabled=True, + _deduplication_enabled=False, ) ): resp = self.client.post("/header_injection/", data={"name": "test"}) From 180d2c31fc8c4555bcb266dac2a208089e0ac452 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Wed, 8 May 2024 16:30:14 +0200 Subject: [PATCH 010/104] feat(iast): enable header injection (#9198) ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. - [x] If change touches code that signs or publishes builds or packages, or handles credentials of any kind, I've requested a review from `@DataDog/security-design-and-guidance`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/appsec/_iast/_patch_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddtrace/appsec/_iast/_patch_modules.py b/ddtrace/appsec/_iast/_patch_modules.py index c2186786dc3..5c38f3d0f62 100644 --- a/ddtrace/appsec/_iast/_patch_modules.py +++ b/ddtrace/appsec/_iast/_patch_modules.py @@ -3,7 +3,7 @@ IAST_PATCH = { "command_injection": True, - "header_injection": False, + "header_injection": True, "path_traversal": True, "weak_cipher": True, "weak_hash": True, From 4c6247b033a7db710995f5aba0822efdc7d951c4 Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Wed, 8 May 2024 11:13:59 -0400 Subject: [PATCH 011/104] fix(grpc): disable aio support (#9182) The goal of this PR is to unblock the 2.9.0 release. This PR hides grpc.aio support behind an internal flag. This integration is broken and will be fixed in a future release. ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Alberto Vara --- ddtrace/contrib/grpc/patch.py | 14 ++++++++++++-- releasenotes/notes/disable-76545e225ff1b3f4.yaml | 4 ++++ riotfile.py | 2 ++ 3 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/disable-76545e225ff1b3f4.yaml diff --git a/ddtrace/contrib/grpc/patch.py b/ddtrace/contrib/grpc/patch.py index 8aedb4788f3..834c460dfa9 100644 --- a/ddtrace/contrib/grpc/patch.py +++ b/ddtrace/contrib/grpc/patch.py @@ -1,3 +1,5 @@ +import os + import grpc from ddtrace import Pin @@ -5,8 +7,10 @@ from ddtrace.internal.schema import schematize_service_name from ddtrace.vendor.wrapt import wrap_function_wrapper as _w +from ...internal.logger import get_logger from ...internal.utils import get_argument_value from ...internal.utils import set_argument_value +from ...internal.utils.formats import asbool from ..trace_utils import unwrap as _u from . import constants from . import utils @@ -15,6 +19,9 @@ from .server_interceptor import create_server_interceptor +log = get_logger(__name__) + + def get_version(): # type: () -> str return getattr(grpc, "__version__", "") @@ -63,6 +70,8 @@ def get_version(): dict( _default_service=schematize_service_name(constants.GRPC_SERVICE_CLIENT), distributed_tracing_enabled=True, + # TODO: Remove this configuration when grpc.aio support is fixed + _grpc_aio_enabled=asbool(os.getenv("_DD_TRACE_GRPC_AIO_ENABLED", False)), ), ) @@ -88,7 +97,8 @@ def get_version(): def patch(): _patch_client() _patch_server() - if HAS_GRPC_AIO: + if HAS_GRPC_AIO and config.grpc._grpc_aio_enabled: + log.debug("The ddtrace grpc aio patch is enabled. This is an experimental feature and may not be stable.") _patch_aio_client() _patch_aio_server() @@ -96,7 +106,7 @@ def patch(): def unpatch(): _unpatch_client() _unpatch_server() - if HAS_GRPC_AIO: + if HAS_GRPC_AIO and config.grpc._grpc_aio_enabled: _unpatch_aio_client() _unpatch_aio_server() diff --git a/releasenotes/notes/disable-76545e225ff1b3f4.yaml b/releasenotes/notes/disable-76545e225ff1b3f4.yaml new file mode 100644 index 00000000000..8a2a3ea1933 --- /dev/null +++ b/releasenotes/notes/disable-76545e225ff1b3f4.yaml @@ -0,0 +1,4 @@ +--- +issues: + - | + grpc: Tracing for the ``grpc.aio`` clients and servers is experimental and may not be stable. This integration is now disabled by default. diff --git a/riotfile.py b/riotfile.py index edf89b9d147..24e1f5244a2 100644 --- a/riotfile.py +++ b/riotfile.py @@ -1736,6 +1736,8 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): "pytest-asyncio": "==0.21.1", "pytest-randomly": latest, }, + # grpc.aio support is broken and disabled by default + env={"_DD_TRACE_GRPC_AIO_ENABLED": "true"}, venvs=[ Venv( pys=select_pys(min_version="3.7", max_version="3.9"), From 7ad2b871323234056e1baa20589c6795176568a6 Mon Sep 17 00:00:00 2001 From: Sam Brenner <106700075+sabrenner@users.noreply.github.com> Date: Wed, 8 May 2024 13:26:30 -0400 Subject: [PATCH 012/104] fix(botocore): add defaults for response types and exception handling for different model providers (#9173) ## Changes Made This PR adds some additional key name checking and adds defaults when `get`ing these attributes. Additionally, adds an additional exception type to catch as a fallback in the case if incorrect response handling. ## Motivation This PR addresses app-crashing cases where different model versions have different key names for certain output fields. Fixes #9135 ## Follow-up A follow-up PR will address the discrepancies in more detail when we have time to go through the model providers and make sure our response parsing is up-to-date. ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: tstadel --- ddtrace/contrib/botocore/services/bedrock.py | 20 ++++++++++++------- ...esponse-key-checking-845ef1f191fcc120.yaml | 4 ++++ 2 files changed, 17 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/bedrock-response-key-checking-845ef1f191fcc120.yaml diff --git a/ddtrace/contrib/botocore/services/bedrock.py b/ddtrace/contrib/botocore/services/bedrock.py index e0896833e1c..dcb1621d93d 100644 --- a/ddtrace/contrib/botocore/services/bedrock.py +++ b/ddtrace/contrib/botocore/services/bedrock.py @@ -167,28 +167,34 @@ def _extract_text_and_response_reason(ctx: core.ExecutionContext, body: Dict[str provider = ctx["model_provider"] try: if provider == _AI21: - text = body.get("completions")[0].get("data").get("text") - finish_reason = body.get("completions")[0].get("finishReason") + completions = body.get("completions", []) + if completions: + data = completions[0].get("data", {}) + text = data.get("text") + finish_reason = completions[0].get("finishReason") elif provider == _AMAZON and "embed" in model_name: text = [body.get("embedding", [])] elif provider == _AMAZON: - text = body.get("results")[0].get("outputText") - finish_reason = body.get("results")[0].get("completionReason") + results = body.get("results", []) + if results: + text = results[0].get("outputText") + finish_reason = results[0].get("completionReason") elif provider == _ANTHROPIC: text = body.get("completion", "") or body.get("content", "") finish_reason = body.get("stop_reason") elif provider == _COHERE and "embed" in model_name: text = body.get("embeddings", [[]]) elif provider == _COHERE: - text = [generation["text"] for generation in body.get("generations")] - finish_reason = [generation["finish_reason"] for generation in body.get("generations")] + generations = body.get("generations", []) + text = [generation["text"] for generation in generations] + finish_reason = [generation["finish_reason"] for generation in generations] elif provider == _META: text = body.get("generation") finish_reason = body.get("stop_reason") elif provider == _STABILITY: # TODO: request/response formats are different for image-based models. Defer for now pass - except (IndexError, AttributeError): + except (IndexError, AttributeError, TypeError): log.warning("Unable to extract text/finish_reason from response body. Defaulting to empty text/finish_reason.") if not isinstance(text, list): diff --git a/releasenotes/notes/bedrock-response-key-checking-845ef1f191fcc120.yaml b/releasenotes/notes/bedrock-response-key-checking-845ef1f191fcc120.yaml new file mode 100644 index 00000000000..ab1480fe64f --- /dev/null +++ b/releasenotes/notes/bedrock-response-key-checking-845ef1f191fcc120.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + botocore: This fix adds additional key name checking and appropriate defaults for responses from Cohere and Amazon models. From ef8a71e0c8fd92ab9d84914fbc645c3f10816785 Mon Sep 17 00:00:00 2001 From: Sam Brenner <106700075+sabrenner@users.noreply.github.com> Date: Wed, 8 May 2024 14:01:41 -0400 Subject: [PATCH 013/104] fix(llmobs): format langchain schema inputs and outputs (#9194) ## Changes Made Changes the parsing of input/output from chain calls using LangChain. Previously, if a `langchain.schema` type was present in the input or output and was passed into a `json.dumps` call, we would catch a "non-serializable JSON" exception, and log it, not appending the message appropriately to the span event. This changes the parsing to go over all attributes of input/outputs, and in the case of a non-string, attempts to map it to a string by accessing its content. ## Motivation Make sure input and output are appropriately tagged on LLMObs span events (fix) for LangChain chains, which are in a readable format (feat, as before it would've read something like `content=xyz` by just stringifying the schema object) ## Notes for Reviewers * No release note included as this is an internal bug fix for LLMObs span event submission. * Most LOC are a couple cassette files added that were needed to test chat model message history in a chain, which we did not have before. ## Testing Some manual verification in the UI was done in addition to the unit tests done. For the LCEL unit test case added, it produces the following in the LLMObs UI: Screenshot 2024-05-07 at 4 16 13 PM ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/llmobs/_integrations/langchain.py | 60 +++++++++--- .../langchain/openai_chain_schema_io.yaml | 88 +++++++++++++++++ .../lcel_openai_chain_schema_io.yaml | 98 +++++++++++++++++++ tests/contrib/langchain/test_langchain.py | 89 ++++++++++++++--- .../langchain/test_langchain_community.py | 91 ++++++++++++++--- 5 files changed, 384 insertions(+), 42 deletions(-) create mode 100644 tests/contrib/langchain/cassettes/langchain/openai_chain_schema_io.yaml create mode 100644 tests/contrib/langchain/cassettes/langchain_community/lcel_openai_chain_schema_io.yaml diff --git a/ddtrace/llmobs/_integrations/langchain.py b/ddtrace/llmobs/_integrations/langchain.py index 701ad968853..d6a8d7e9ea7 100644 --- a/ddtrace/llmobs/_integrations/langchain.py +++ b/ddtrace/llmobs/_integrations/langchain.py @@ -149,23 +149,25 @@ def _llmobs_set_meta_tags_from_chain( span.set_tag_str(SPAN_KIND, "workflow") if inputs is not None: - if isinstance(inputs, str): - span.set_tag_str(INPUT_VALUE, inputs) - else: - try: - span.set_tag_str(INPUT_VALUE, json.dumps(inputs)) - except TypeError: - log.warning("Failed to serialize chain input data to JSON: %s", inputs) + try: + formatted_inputs = self.format_io(inputs) + if isinstance(formatted_inputs, str): + span.set_tag_str(INPUT_VALUE, formatted_inputs) + else: + span.set_tag_str(INPUT_VALUE, json.dumps(self.format_io(inputs))) + except TypeError: + log.warning("Failed to serialize chain input data to JSON") if error: span.set_tag_str(OUTPUT_VALUE, "") elif outputs is not None: - if isinstance(outputs, str): - span.set_tag_str(OUTPUT_VALUE, str(outputs)) - else: - try: - span.set_tag_str(OUTPUT_VALUE, json.dumps(outputs)) - except TypeError: - log.warning("Failed to serialize chain output data to JSON: %s", outputs) + try: + formatted_outputs = self.format_io(outputs) + if isinstance(formatted_outputs, str): + span.set_tag_str(OUTPUT_VALUE, formatted_outputs) + else: + span.set_tag_str(OUTPUT_VALUE, json.dumps(self.format_io(outputs))) + except TypeError: + log.warning("Failed to serialize chain output data to JSON") def _set_base_span_tags( # type: ignore[override] self, @@ -234,3 +236,33 @@ def record_usage(self, span: Span, usage: Dict[str, Any]) -> None: total_cost = span.get_metric(TOTAL_COST) if total_cost: self.metric(span, "incr", "tokens.total_cost", total_cost) + + def format_io( + self, + messages, + ): + """ + Formats input and output messages for serialization to JSON. + Specifically, makes sure that any schema messages are converted to strings appropriately. + """ + if isinstance(messages, dict): + formatted = {} + for key, value in messages.items(): + formatted[key] = self.format_io(value) + return formatted + if isinstance(messages, list): + return [self.format_io(message) for message in messages] + return self.get_content_from_message(messages) + + def get_content_from_message(self, message) -> str: + """ + Attempts to extract the content and role from a message (AIMessage, HumanMessage, SystemMessage) object. + """ + if isinstance(message, str): + return message + try: + content = getattr(message, "__dict__", {}).get("content", str(message)) + role = getattr(message, "role", ROLE_MAPPING.get(getattr(message, "type"), "")) + return (role, content) if role else content + except AttributeError: + return str(message) diff --git a/tests/contrib/langchain/cassettes/langchain/openai_chain_schema_io.yaml b/tests/contrib/langchain/cassettes/langchain/openai_chain_schema_io.yaml new file mode 100644 index 00000000000..f5d85e10ded --- /dev/null +++ b/tests/contrib/langchain/cassettes/langchain/openai_chain_schema_io.yaml @@ -0,0 +1,88 @@ +interactions: +- request: + body: '{"messages": [{"role": "system", "content": "You''re an assistant who''s + good at world capitals. Respond in 20 words or fewer"}, {"role": "user", "content": + "Can you be my science teacher instead?"}, {"role": "assistant", "content": + "Yes"}, {"role": "user", "content": "What''s the powerhouse of the cell?"}], + "model": "gpt-3.5-turbo", "max_tokens": null, "stream": false, "n": 1, "temperature": + 0.7}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '397' + Content-Type: + - application/json + User-Agent: + - OpenAI/v1 PythonBindings/0.27.8 + X-OpenAI-Client-User-Agent: + - '{"bindings_version": "0.27.8", "httplib": "requests", "lang": "python", "lang_version": + "3.10.13", "platform": "macOS-13.6.5-arm64-arm-64bit", "publisher": "openai", + "uname": "Darwin 22.6.0 Darwin Kernel Version 22.6.0: Mon Feb 19 19:45:09 + PST 2024; root:xnu-8796.141.3.704.6~1/RELEASE_ARM64_T6000 arm64 arm"}' + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: "{\n \"id\": \"chatcmpl-9MKlPkyelxszTnEysy4eeZihFwATX\",\n \"object\": + \"chat.completion\",\n \"created\": 1715110059,\n \"model\": \"gpt-3.5-turbo-0125\",\n + \ \"choices\": [\n {\n \"index\": 0,\n \"message\": {\n \"role\": + \"assistant\",\n \"content\": \"Mitochondria.\"\n },\n \"logprobs\": + null,\n \"finish_reason\": \"stop\"\n }\n ],\n \"usage\": {\n \"prompt_tokens\": + 54,\n \"completion_tokens\": 4,\n \"total_tokens\": 58\n },\n \"system_fingerprint\": + null\n}\n" + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 88039bced98e1841-EWR + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 07 May 2024 19:27:39 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=aG4mqwdLl6mtVHmUY1sni3SuU.qFBxanrCQAi0keMns-1715110059-1.0.1.1-q49zqtqqYOldKfp6LoZrV4xRF21gLaurG_WZtpCCQwxZkgKJfJgRMp5UzCBiwPlyxeQ4_OU81cwxx2_3QluxUw; + path=/; expires=Tue, 07-May-24 19:57:39 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=haGbmuprDHtB3GBdFJZDKT3WJMffTyEMnAhIgX__yro-1715110059674-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - user-vgqng3jybrjkfe7a1gf2l5ch + openai-processing-ms: + - '194' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=15724800; includeSubDomains + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '60000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '59941' + x-ratelimit-reset-requests: + - 8.64s + x-ratelimit-reset-tokens: + - 59ms + x-request-id: + - req_0d67fc7c57498b6f769c94d0e1b8ed94 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/contrib/langchain/cassettes/langchain_community/lcel_openai_chain_schema_io.yaml b/tests/contrib/langchain/cassettes/langchain_community/lcel_openai_chain_schema_io.yaml new file mode 100644 index 00000000000..82076d76e4e --- /dev/null +++ b/tests/contrib/langchain/cassettes/langchain_community/lcel_openai_chain_schema_io.yaml @@ -0,0 +1,98 @@ +interactions: +- request: + body: '{"messages": [{"role": "system", "content": "You''re an assistant who''s + good at world capitals. Respond in 20 words or fewer"}, {"role": "user", "content": + "Can you be my science teacher instead?"}, {"role": "assistant", "content": + "Yes"}, {"role": "user", "content": "What''s the powerhouse of the cell?"}], + "model": "gpt-3.5-turbo", "n": 1, "stream": false, "temperature": 0.7}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '377' + content-type: + - application/json + host: + - api.openai.com + user-agent: + - OpenAI/Python 1.12.0 + x-stainless-arch: + - arm64 + x-stainless-async: + - 'false' + x-stainless-lang: + - python + x-stainless-os: + - MacOS + x-stainless-package-version: + - 1.12.0 + x-stainless-runtime: + - CPython + x-stainless-runtime-version: + - 3.10.13 + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: !!binary | + H4sIAAAAAAAAA1SQy2rDMBBF9/4KMes42G1tJ16GQiElm2ZTKCXI8sRWKmuENIaWkH8vcl7tRou5 + cy5ndEyEAN1CLUD1ktXgTLrcrMdteXit1PPby5bsat9lC910y7Zcv8MsEtQcUPGVmisanEHWZM+x + 8igZY2te5UWelVVVTsFALZqIdY7Tx3mR8ugbSrP8obiQPWmFAWrxkQghxHF6o6Nt8Rtqkc2ukwFD + kB1CfVsSAjyZOAEZgg4sLcPsHiqyjHbS3mgm1ZNtvZZzuKycbt2GOuepiR52NOY232urQ7/zKAPZ + 2BOY3Bk/JUJ8TjeM/7TAeRoc75i+0MbC4ulcB/dfu4fXjIml+cMskosehJ/AOOz22nbondfTPVEy + OSW/AAAA//8DAP/BSKLOAQAA + headers: + CF-Cache-Status: + - DYNAMIC + CF-RAY: + - 88034ba91f9132ee-EWR + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json + Date: + - Tue, 07 May 2024 18:32:56 GMT + Server: + - cloudflare + Set-Cookie: + - __cf_bm=tXnAARlh82B5PYxg8iwWtANjfsCqEVAPX9CNpGTnQlA-1715106776-1.0.1.1-Bgtuv8XOKJRrUkgcVrqMx771X7Ib6JMU509irL4q2Mw4kX12I9lvevhCOh3cWOFtC3ZeznyRxeOrvVe1g5JpKw; + path=/; expires=Tue, 07-May-24 19:02:56 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=4x6F.SrUVWZ6jyp1ltLrRzvWKvjujNgeIUIYDknJUuQ-1715106776974-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + Transfer-Encoding: + - chunked + alt-svc: + - h3=":443"; ma=86400 + openai-organization: + - user-vgqng3jybrjkfe7a1gf2l5ch + openai-processing-ms: + - '363' + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=15724800; includeSubDomains + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '60000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '59941' + x-ratelimit-reset-requests: + - 8.64s + x-ratelimit-reset-tokens: + - 59ms + x-request-id: + - req_826f1c983f57bdccb2e5a88b97e35732 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/contrib/langchain/test_langchain.py b/tests/contrib/langchain/test_langchain.py index 466966f939b..3597c2efbee 100644 --- a/tests/contrib/langchain/test_langchain.py +++ b/tests/contrib/langchain/test_langchain.py @@ -1296,10 +1296,11 @@ def _expected_llmobs_chain_call(span, metadata=None, input_value=None, output_va ) @staticmethod - def _expected_llmobs_llm_call(span, provider="openai", input_role=None, output_role=None): - input_meta = {"content": mock.ANY} - if input_role is not None: - input_meta["role"] = input_role + def _expected_llmobs_llm_call(span, provider="openai", input_roles=[None], output_role=None): + input_meta = [{"content": mock.ANY} for _ in input_roles] + for idx, role in enumerate(input_roles): + if role is not None: + input_meta[idx]["role"] = role output_meta = {"content": mock.ANY} if output_role is not None: @@ -1326,7 +1327,7 @@ def _expected_llmobs_llm_call(span, provider="openai", input_role=None, output_r span, model_name=span.get_tag("langchain.request.model"), model_provider=span.get_tag("langchain.request.provider"), - input_messages=[input_meta], + input_messages=input_meta, output_messages=[output_meta], metadata=metadata, token_metrics={}, @@ -1344,7 +1345,7 @@ def _test_llmobs_llm_invoke( mock_llmobs_span_writer, mock_tracer, cassette_name, - input_role=None, + input_roles=[None], output_role=None, different_py39_cassette=False, ): @@ -1363,7 +1364,7 @@ def _test_llmobs_llm_invoke( cls._expected_llmobs_llm_call( span, provider=provider, - input_role=input_role, + input_roles=input_roles, output_role=output_role, ) ), @@ -1380,7 +1381,7 @@ def _test_llmobs_chain_invoke( mock_llmobs_span_writer, mock_tracer, cassette_name, - expected_spans_data=[("llm", {"provider": "openai", "input_role": None, "output_role": None})], + expected_spans_data=[("llm", {"provider": "openai", "input_roles": [None], "output_role": None})], different_py39_cassette=False, ): # disable the service before re-enabling it, as it was enabled in another test @@ -1463,7 +1464,7 @@ def test_llmobs_openai_chat_model(self, langchain, mock_llmobs_span_writer, mock mock_tracer=mock_tracer, cassette_name="openai_chat_completion_sync_call.yaml", provider="openai", - input_role="user", + input_roles=["user"], output_role="assistant", different_py39_cassette=True, ) @@ -1478,7 +1479,7 @@ def test_llmobs_openai_chat_model_custom_role(self, langchain, mock_llmobs_span_ mock_tracer=mock_tracer, cassette_name="openai_chat_completion_sync_call.yaml", provider="openai", - input_role="custom", + input_roles=["custom"], output_role="assistant", different_py39_cassette=True, ) @@ -1523,7 +1524,7 @@ def test_llmobs_chain(self, langchain, mock_llmobs_span_writer, mock_tracer, req ), }, ), - ("llm", {"provider": "openai", "input_role": None, "output_role": None}), + ("llm", {"provider": "openai", "input_roles": [None], "output_role": None}), ], different_py39_cassette=True, ) @@ -1585,7 +1586,7 @@ def test_llmobs_chain_nested(self, langchain, mock_llmobs_span_writer, mock_trac "output_value": mock.ANY, }, ), - ("llm", {"provider": "openai", "input_role": None, "output_role": None}), + ("llm", {"provider": "openai", "input_roles": [None], "output_role": None}), ( "chain", { @@ -1593,6 +1594,68 @@ def test_llmobs_chain_nested(self, langchain, mock_llmobs_span_writer, mock_trac "output_value": mock.ANY, }, ), - ("llm", {"provider": "openai", "input_role": None, "output_role": None}), + ("llm", {"provider": "openai", "input_roles": [None], "output_role": None}), + ], + ) + + @pytest.mark.skipif(sys.version_info < (3, 10, 0), reason="Requires unnecessary cassette file for Python 3.9") + def test_llmobs_chain_schema_io(self, langchain, mock_llmobs_span_writer, mock_tracer, request_vcr): + model = langchain.chat_models.ChatOpenAI(temperature=0, max_tokens=256) + prompt = langchain.prompts.ChatPromptTemplate.from_messages( + [ + langchain.prompts.SystemMessagePromptTemplate.from_template( + "You're an assistant who's good at {ability}. Respond in 20 words or fewer" + ), + langchain.prompts.MessagesPlaceholder(variable_name="history"), + langchain.prompts.HumanMessagePromptTemplate.from_template("{input}"), + ] + ) + + chain = langchain.chains.LLMChain(prompt=prompt, llm=model) + + self._test_llmobs_chain_invoke( + generate_trace=lambda input_text: chain.run( + { + "ability": "world capitals", + "history": [ + langchain.schema.HumanMessage(content="Can you be my science teacher instead?"), + langchain.schema.AIMessage(content="Yes"), + ], + "input": "What's the powerhouse of the cell?", + } + ), + request_vcr=request_vcr, + mock_llmobs_span_writer=mock_llmobs_span_writer, + mock_tracer=mock_tracer, + cassette_name="openai_chain_schema_io.yaml", + expected_spans_data=[ + ( + "chain", + { + "input_value": json.dumps( + { + "ability": "world capitals", + "history": [["user", "Can you be my science teacher instead?"], ["assistant", "Yes"]], + "input": "What's the powerhouse of the cell?", + } + ), + "output_value": json.dumps( + { + "ability": "world capitals", + "history": [["user", "Can you be my science teacher instead?"], ["assistant", "Yes"]], + "input": "What's the powerhouse of the cell?", + "text": "Mitochondria.", + } + ), + }, + ), + ( + "llm", + { + "provider": "openai", + "input_roles": ["system", "user", "assistant", "user"], + "output_role": "assistant", + }, + ), ], ) diff --git a/tests/contrib/langchain/test_langchain_community.py b/tests/contrib/langchain/test_langchain_community.py index 2932b725aad..7d5a2fca65e 100644 --- a/tests/contrib/langchain/test_langchain_community.py +++ b/tests/contrib/langchain/test_langchain_community.py @@ -1284,10 +1284,11 @@ def _expected_llmobs_chain_call(span, input_parameters=None, input_value=None, o ) @staticmethod - def _expected_llmobs_llm_call(span, provider="openai", input_role=None, output_role=None): - input_meta = {"content": mock.ANY} - if input_role is not None: - input_meta["role"] = input_role + def _expected_llmobs_llm_call(span, provider="openai", input_roles=[None], output_role=None): + input_meta = [{"content": mock.ANY} for _ in input_roles] + for idx, role in enumerate(input_roles): + if role is not None: + input_meta[idx]["role"] = role output_meta = {"content": mock.ANY} if output_role is not None: @@ -1314,7 +1315,7 @@ def _expected_llmobs_llm_call(span, provider="openai", input_role=None, output_r span, model_name=span.get_tag("langchain.request.model"), model_provider=span.get_tag("langchain.request.provider"), - input_messages=[input_meta], + input_messages=input_meta, output_messages=[output_meta], metadata=metadata, token_metrics={}, @@ -1332,7 +1333,7 @@ def _test_llmobs_llm_invoke( mock_llmobs_span_writer, mock_tracer, cassette_name, - input_role=None, + input_roles=[None], output_role=None, ): LLMObs.disable() @@ -1348,7 +1349,7 @@ def _test_llmobs_llm_invoke( cls._expected_llmobs_llm_call( span, provider=provider, - input_role=input_role, + input_roles=input_roles, output_role=output_role, ) ), @@ -1365,7 +1366,7 @@ def _test_llmobs_chain_invoke( mock_llmobs_span_writer, mock_tracer, cassette_name, - expected_spans_data=[("llm", {"provider": "openai", "input_role": None, "output_role": None})], + expected_spans_data=[("llm", {"provider": "openai", "input_roles": [None], "output_role": None})], ): # disable the service before re-enabling it, as it was enabled in another test LLMObs.disable() @@ -1429,7 +1430,7 @@ def test_llmobs_openai_chat_model(self, langchain_openai, mock_llmobs_span_write mock_tracer=mock_tracer, cassette_name="openai_chat_completion_sync_call.yaml", provider="openai", - input_role="user", + input_roles=["user"], output_role="assistant", ) @@ -1446,7 +1447,7 @@ def test_llmobs_openai_chat_model_custom_role( mock_tracer=mock_tracer, cassette_name="openai_chat_completion_sync_call.yaml", provider="openai", - input_role="custom", + input_roles=["custom"], output_role="assistant", ) @@ -1484,7 +1485,7 @@ def test_llmobs_chain(self, langchain_core, langchain_openai, mock_llmobs_span_w "output_value": expected_output, }, ), - ("llm", {"provider": "openai", "input_role": None, "output_role": None}), + ("llm", {"provider": "openai", "input_roles": [None], "output_role": None}), ], ) @@ -1526,8 +1527,8 @@ def test_llmobs_chain_nested( "output_value": mock.ANY, }, ), - ("llm", {"provider": "openai", "input_role": "user", "output_role": "assistant"}), - ("llm", {"provider": "openai", "input_role": "user", "output_role": "assistant"}), + ("llm", {"provider": "openai", "input_roles": ["user"], "output_role": "assistant"}), + ("llm", {"provider": "openai", "input_roles": ["user"], "output_role": "assistant"}), ], ) @@ -1554,7 +1555,67 @@ def test_llmobs_chain_batch( "output_value": mock.ANY, }, ), - ("llm", {"provider": "openai", "input_role": "user", "output_role": "assistant"}), - ("llm", {"provider": "openai", "input_role": "user", "output_role": "assistant"}), + ("llm", {"provider": "openai", "input_roles": ["user"], "output_role": "assistant"}), + ("llm", {"provider": "openai", "input_roles": ["user"], "output_role": "assistant"}), + ], + ) + + @flaky(1735812000) + def test_llmobs_chain_schema_io( + self, langchain_core, langchain_openai, mock_llmobs_span_writer, mock_tracer, request_vcr + ): + model = langchain_openai.ChatOpenAI() + prompt = langchain_core.prompts.ChatPromptTemplate.from_messages( + [ + ("system", "You're an assistant who's good at {ability}. Respond in 20 words or fewer"), + langchain_core.prompts.MessagesPlaceholder(variable_name="history"), + ("human", "{input}"), + ] + ) + + chain = prompt | model + + self._test_llmobs_chain_invoke( + generate_trace=lambda inputs: chain.invoke( + { + "ability": "world capitals", + "history": [ + langchain.schema.HumanMessage(content="Can you be my science teacher instead?"), + langchain.schema.AIMessage(content="Yes"), + ], + "input": "What's the powerhouse of the cell?", + } + ), + request_vcr=request_vcr, + mock_llmobs_span_writer=mock_llmobs_span_writer, + mock_tracer=mock_tracer, + cassette_name="lcel_openai_chain_schema_io.yaml", + expected_spans_data=[ + ( + "chain", + { + "input_value": json.dumps( + [ + { + "ability": "world capitals", + "history": [ + ["user", "Can you be my science teacher instead?"], + ["assistant", "Yes"], + ], + "input": "What's the powerhouse of the cell?", + } + ] + ), + "output_value": json.dumps(["assistant", "Mitochondria."]), + }, + ), + ( + "llm", + { + "provider": "openai", + "input_roles": ["system", "user", "assistant", "user"], + "output_role": "assistant", + }, + ), ], ) From 69f91eeac6e57d963ba9d795e8e52ccc0cf8c7c7 Mon Sep 17 00:00:00 2001 From: Zachary Groves <32471391+ZStriker19@users.noreply.github.com> Date: Wed, 8 May 2024 15:01:06 -0400 Subject: [PATCH 014/104] fix(remoteconfig): fix remoteconfig when flare data in payload (#9196) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remote config was broken entirely due to the addition of tracer flare data in the config sent from the agent. Example incoming RC config: ``` data = {'metadata': [{'id': 'configuration_order', 'product_name': 'AGENT_CONFIG', 'sha256_hash': 'ddfc2c7b5ee1710aa915edfccd8a0d452784d946cebae0554485b5c0539a9e2c', 'length': 198, 'tuf_version': 2, 'apply_state': 2, 'apply_error': None}, {'id': 'f6c80fdcc00b702c54ff6ae5ff2ac7f16d9afef109bdf53ee990376455301ab2', 'product_name': 'APM_TRACING', 'sha256_hash': '098cb5a0d27fce648cdd4c6e686038282b64ffee7f42b7238a78552c91948d11', 'length': 616, 'tuf_version': 3, 'apply_state': 2, 'apply_error': None}], 'config': [{'internal_order': ['flare-log-level.trace', 'flare-log-level.debug', 'flare-log-level.info', 'flare-log-level.warn', 'flare-log-level.error', 'flare-log-level.critical', 'flare-log-level.off'], 'order': []}, {'id': 'f6c80fdcc00b702c54ff6ae5ff2ac7f16d9afef109bdf53ee990376455301ab2', 'revision': 1715109076236, 'schema_version': 'v1.0.0', 'action': 'enable', 'lib_config': {'library_language': 'all', 'library_version': 'latest', 'service_name': 'zachs-python-app', 'env': 'zachariah', 'tracing_enabled': True, 'dynamic_sampling_enabled': False, 'tracing_tags': ['rc:works'], 'tracing_sampling_rules': [{'service': 'zachs-python-app', 'provenance': 'customer', 'resource': 'GET /', 'sample_rate': 0.01}, {'service': 'zachs-python-app', 'provenance': 'customer', 'resource': '', 'sample_rate': 1}]}, 'service_target': {'service': 'zachs-python-app', 'env': 'zachariah'}}], 'shared_data_counter': 1} ``` The python tracer’s implementation of pulling RC rules was brittle and relied upon data["config"][0] always having the lib_config dict. However, with tracer flares it seems the agent sometimes sends the payload with the flare info in that 0 position in the list, so instead we need to sometimes grab data["config"][1] . ## Checklist - [ ] Change(s) are motivated and described in the PR description - [ ] Testing strategy is described if automated tests are not included in the PR - [ ] Risks are described (performance impact, potential for breakage, maintainability) - [ ] Change is maintainable (easy to change, telemetry, documentation) - [ ] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [ ] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [ ] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [ ] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [ ] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/settings/config.py | 10 ++++++++-- .../notes/rc_payload_fix-a03a98dcad934658.yaml | 8 ++++++++ tests/internal/test_settings.py | 18 ++++++++++++++++-- 3 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/rc_payload_fix-a03a98dcad934658.yaml diff --git a/ddtrace/settings/config.py b/ddtrace/settings/config.py index 972d2fff700..10cc15a5828 100644 --- a/ddtrace/settings/config.py +++ b/ddtrace/settings/config.py @@ -743,8 +743,15 @@ def _handle_remoteconfig(self, data, test_tracer=None): log.warning("unexpected number of RC payloads %r", data) return + # Check if 'lib_config' is a key in the dictionary since other items can be sent in the payload + config = None + for config_item in data["config"]: + if isinstance(config_item, Dict): + if "lib_config" in config_item: + config = config_item + break + # If no data is submitted then the RC config has been deleted. Revert the settings. - config = data["config"][0] base_rc_config = {n: None for n in self._config} if config and "lib_config" in config: @@ -777,7 +784,6 @@ def _handle_remoteconfig(self, data, test_tracer=None): if tags: tags = self._format_tags(lib_config["tracing_header_tags"]) base_rc_config["trace_http_header_tags"] = tags - self._set_config_items([(k, v, "remote_config") for k, v in base_rc_config.items()]) # called unconditionally to handle the case where header tags have been unset self._handle_remoteconfig_header_tags(base_rc_config) diff --git a/releasenotes/notes/rc_payload_fix-a03a98dcad934658.yaml b/releasenotes/notes/rc_payload_fix-a03a98dcad934658.yaml new file mode 100644 index 00000000000..330c6586eb9 --- /dev/null +++ b/releasenotes/notes/rc_payload_fix-a03a98dcad934658.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - | + RemoteConfig: This fix resolves an issue where remote config did not work for the tracer when using an agent + that would add a flare item to the remote config payload. With this fix, the tracer will now correctly pull out + the lib_config we need from the payload in order to implement remote config changes properly. + + diff --git a/tests/internal/test_settings.py b/tests/internal/test_settings.py index 4bae14bfef9..0ad706b3951 100644 --- a/tests/internal/test_settings.py +++ b/tests/internal/test_settings.py @@ -16,11 +16,25 @@ def _base_rc_config(cfg): return { "metadata": [], "config": [ + # this flare data can often come in and we want to make sure we're pulling the + # actual lib_config data out correctly regardless + { + "internal_order": [ + "flare-log-level.trace", + "flare-log-level.debug", + "flare-log-level.info", + "flare-log-level.warn", + "flare-log-level.error", + "flare-log-level.critical", + "flare-log-level.off", + ], + "order": [], + }, { "action": "enable", "service_target": {"service": None, "env": None}, "lib_config": cfg, - } + }, ], } @@ -182,7 +196,7 @@ def test_settings_missing_lib_config(config, monkeypatch): base_rc_config = _base_rc_config({}) # Delete "lib_config" from the remote config - del base_rc_config["config"][0]["lib_config"] + del base_rc_config["config"][1]["lib_config"] assert "lib_config" not in base_rc_config["config"][0] config._handle_remoteconfig(base_rc_config, None) From 0346f71707f4760caeed3e3e646b412a84196a0a Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Wed, 8 May 2024 22:51:43 +0100 Subject: [PATCH 015/104] chore(di): include globals in snapshots (#9184) We include the value of referenced globals in enriched log messages. We use the `statics` fields for carrying this extra information. ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/debugging/_safety.py | 2 +- ddtrace/debugging/_signal/snapshot.py | 22 ++++++++++++++++++---- tests/debugging/test_debugger.py | 1 + tests/debugging/test_encoding.py | 13 +++++++------ tests/debugging/test_safety.py | 13 +++++++++++++ 5 files changed, 40 insertions(+), 11 deletions(-) diff --git a/ddtrace/debugging/_safety.py b/ddtrace/debugging/_safety.py index 50142fc87bc..118deddef40 100644 --- a/ddtrace/debugging/_safety.py +++ b/ddtrace/debugging/_safety.py @@ -32,7 +32,7 @@ def get_locals(frame: FrameType) -> Iterator[Tuple[str, Any]]: def get_globals(frame: FrameType) -> Iterator[Tuple[str, Any]]: nonlocal_names = frame.f_code.co_names - _globals = globals() + _globals = frame.f_globals return ((name, _globals[name]) for name in nonlocal_names if name in _globals) diff --git a/ddtrace/debugging/_signal/snapshot.py b/ddtrace/debugging/_signal/snapshot.py index ed0bd042098..d9f5ec99cf3 100644 --- a/ddtrace/debugging/_signal/snapshot.py +++ b/ddtrace/debugging/_signal/snapshot.py @@ -38,6 +38,7 @@ def _capture_context( arguments: List[Tuple[str, Any]], _locals: List[Tuple[str, Any]], + _globals: List[Tuple[str, Any]], throwable: ExcInfoType, limits: CaptureLimits = DEFAULT_CAPTURE_LIMITS, ) -> Dict[str, Any]: @@ -50,18 +51,29 @@ def timeout(_): "arguments": utils.capture_pairs( arguments, limits.max_level, limits.max_len, limits.max_size, limits.max_fields, timeout ) - if arguments is not None + if arguments else {}, "locals": utils.capture_pairs( _locals, limits.max_level, limits.max_len, limits.max_size, limits.max_fields, timeout ) - if _locals is not None + if _locals + else {}, + "staticFields": utils.capture_pairs( + _globals, limits.max_level, limits.max_len, limits.max_size, limits.max_fields, timeout + ) + if _globals else {}, "throwable": utils.capture_exc_info(throwable), } -_EMPTY_CAPTURED_CONTEXT = _capture_context([], [], (None, None, None), DEFAULT_CAPTURE_LIMITS) +_EMPTY_CAPTURED_CONTEXT = _capture_context( + arguments=[], + _locals=[], + _globals=[], + throwable=(None, None, None), + limits=DEFAULT_CAPTURE_LIMITS, +) @attr.s @@ -121,6 +133,7 @@ def enter(self): self.entry_capture = _capture_context( _args, [], + [], (None, None, None), limits=probe.limits, ) @@ -154,7 +167,7 @@ def exit(self, retval, exc_info, duration): if probe.take_snapshot: self.return_capture = _capture_context( - self.args or _safety.get_args(self.frame), _locals, exc_info, limits=probe.limits + self.args or _safety.get_args(self.frame), _locals, [], exc_info, limits=probe.limits ) self.duration = duration self.state = SignalState.DONE @@ -179,6 +192,7 @@ def line(self): self.line_capture = _capture_context( self.args or _safety.get_args(frame), _safety.get_locals(frame), + _safety.get_globals(frame), sys.exc_info(), limits=probe.limits, ) diff --git a/tests/debugging/test_debugger.py b/tests/debugging/test_debugger.py index a2be8b87529..22dbe1060ea 100644 --- a/tests/debugging/test_debugger.py +++ b/tests/debugging/test_debugger.py @@ -1197,6 +1197,7 @@ def test_debugger_redacted_identifiers(): "size": 3, }, }, + "staticFields": {"SensitiveData": {"type": "type", "value": ""}}, "throwable": None, } diff --git a/tests/debugging/test_encoding.py b/tests/debugging/test_encoding.py index 4c5b2e45170..b7a13d25e6e 100644 --- a/tests/debugging/test_encoding.py +++ b/tests/debugging/test_encoding.py @@ -136,13 +136,13 @@ def c(): def test_capture_context_default_level(): - context = _capture_context([("self", tree)], [], (None, None, None), CaptureLimits(max_level=0)) + context = _capture_context([("self", tree)], [], [], (None, None, None), CaptureLimits(max_level=0)) self = context["arguments"]["self"] assert self["fields"]["root"]["notCapturedReason"] == "depth" def test_capture_context_one_level(): - context = _capture_context([("self", tree)], [], (None, None, None), CaptureLimits(max_level=1)) + context = _capture_context([("self", tree)], [], [], (None, None, None), CaptureLimits(max_level=1)) self = context["arguments"]["self"] assert self["fields"]["root"]["fields"]["left"] == {"notCapturedReason": "depth", "type": "Node"} @@ -152,13 +152,13 @@ def test_capture_context_one_level(): def test_capture_context_two_level(): - context = _capture_context([("self", tree)], [], (None, None, None), CaptureLimits(max_level=2)) + context = _capture_context([("self", tree)], [], [], (None, None, None), CaptureLimits(max_level=2)) self = context["arguments"]["self"] assert self["fields"]["root"]["fields"]["left"]["fields"]["right"] == {"notCapturedReason": "depth", "type": "Node"} def test_capture_context_three_level(): - context = _capture_context([("self", tree)], [], (None, None, None), CaptureLimits(max_level=3)) + context = _capture_context([("self", tree)], [], [], (None, None, None), CaptureLimits(max_level=3)) self = context["arguments"]["self"] assert self["fields"]["root"]["fields"]["left"]["fields"]["right"]["fields"]["right"]["isNull"], context assert self["fields"]["root"]["fields"]["left"]["fields"]["right"]["fields"]["left"]["isNull"], context @@ -169,11 +169,12 @@ def test_capture_context_exc(): try: raise Exception("test", "me") except Exception: - context = _capture_context([], [], sys.exc_info()) + context = _capture_context([], [], [], sys.exc_info()) exc = context.pop("throwable") assert context == { "arguments": {}, "locals": {}, + "staticFields": {}, } assert exc["message"] == "'test', 'me'" assert exc["type"] == "Exception" @@ -190,7 +191,7 @@ def test_batch_json_encoder(): # to test that we can handle unicode strings. cake = "After the test there will be ✨ 🍰 ✨ in the annex" - buffer_size = 30 * (1 << 10) + buffer_size = 30 * (1 << 20) queue = SignalQueue(encoder=LogSignalJsonEncoder(None), buffer_size=buffer_size) s.line() diff --git a/tests/debugging/test_safety.py b/tests/debugging/test_safety.py index e0f1b858719..3acb0288924 100644 --- a/tests/debugging/test_safety.py +++ b/tests/debugging/test_safety.py @@ -7,6 +7,9 @@ from ddtrace.debugging import _safety +GLOBAL_VALUE = 42 + + def test_get_args(): def assert_args(args): assert set(dict(_safety.get_args(inspect.currentframe().f_back)).keys()) == args @@ -14,6 +17,9 @@ def assert_args(args): def assert_locals(_locals): assert set(dict(_safety.get_locals(inspect.currentframe().f_back)).keys()) == _locals + def assert_globals(_globals): + assert set(dict(_safety.get_globals(inspect.currentframe().f_back)).keys()) == _globals + def arg_and_kwargs(a, **kwargs): assert_args({"a", "kwargs"}) assert_locals(set()) @@ -30,10 +36,17 @@ def args(*ars): assert_args({"ars"}) assert_locals(set()) + def referenced_globals(): + global GLOBAL_VALUE + a = GLOBAL_VALUE >> 1 # noqa + + assert_globals({"GLOBAL_VALUE"}) + arg_and_kwargs(1, b=2) arg_and_args_and_kwargs(1, 42, b=2) args_and_kwargs() args() + referenced_globals() # ---- Side effects ---- From 7e5d79be43a9218cb5314fbdd4877953512534c0 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Thu, 9 May 2024 10:17:06 +0100 Subject: [PATCH 016/104] chore: skip non-user frames in exception replay (#9015) We skip non-user frames when collecting snapshots as part of the exception replay feature. ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [ ] Title is accurate - [ ] All changes are related to the pull request's stated goal - [ ] Description motivates each change - [ ] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [ ] Testing strategy adequately addresses listed risks - [ ] Change is maintainable (easy to change, telemetry, documentation) - [ ] Release note makes sense to a user of the library - [ ] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [ ] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/debugging/_exception/auto_instrument.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ddtrace/debugging/_exception/auto_instrument.py b/ddtrace/debugging/_exception/auto_instrument.py index 43dfda6bcee..36a4bbbe354 100644 --- a/ddtrace/debugging/_exception/auto_instrument.py +++ b/ddtrace/debugging/_exception/auto_instrument.py @@ -1,5 +1,6 @@ from collections import deque from itertools import count +from pathlib import Path import sys from threading import current_thread from types import FrameType @@ -16,6 +17,7 @@ from ddtrace.debugging._signal.collector import SignalCollector from ddtrace.debugging._signal.snapshot import DEFAULT_CAPTURE_LIMITS from ddtrace.debugging._signal.snapshot import Snapshot +from ddtrace.internal.packages import is_user_code from ddtrace.internal.rate_limiter import BudgetRateLimiterWithJitter as RateLimiter from ddtrace.internal.rate_limiter import RateLimitExceeded @@ -176,8 +178,8 @@ def on_span_finish(self, span: Span) -> None: code = frame.f_code seq_nr = next(seq) - # TODO: Check if it is user code; if not, skip. We still - # generate a sequence number. + if not is_user_code(Path(frame.f_code.co_filename)): + continue snapshot_id = frame.f_locals.get(SNAPSHOT_KEY, None) if snapshot_id is None: From a01e103446ac5d2e06e87fc65353561cc2ea8219 Mon Sep 17 00:00:00 2001 From: Juanjo Alvarez Martinez Date: Thu, 9 May 2024 13:32:14 +0200 Subject: [PATCH 017/104] chore: mark telemetry test as flaky (#9208) ## Checklist - [X] Change(s) are motivated and described in the PR description - [X] Testing strategy is described if automated tests are not included in the PR - [X] Risks are described (performance impact, potential for breakage, maintainability) - [X] Change is maintainable (easy to change, telemetry, documentation) - [X] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [X] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [X] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [X] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [ ] Title is accurate - [ ] All changes are related to the pull request's stated goal - [ ] Description motivates each change - [ ] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [ ] Testing strategy adequately addresses listed risks - [ ] Change is maintainable (easy to change, telemetry, documentation) - [ ] Release note makes sense to a user of the library - [ ] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [ ] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Signed-off-by: Juanjo Alvarez --- tests/appsec/iast/test_telemetry.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/appsec/iast/test_telemetry.py b/tests/appsec/iast/test_telemetry.py index 0b52bd72077..7fec5330dff 100644 --- a/tests/appsec/iast/test_telemetry.py +++ b/tests/appsec/iast/test_telemetry.py @@ -17,6 +17,7 @@ from ddtrace.internal.telemetry.constants import TELEMETRY_TYPE_GENERATE_METRICS from tests.appsec.iast.aspects.conftest import _iast_patched_module from tests.utils import DummyTracer +from tests.utils import flaky from tests.utils import override_env from tests.utils import override_global_config @@ -86,6 +87,7 @@ def test_metric_instrumented_propagation(telemetry_writer): assert [metric.name for metric in generate_metrics.values()] == ["instrumented.propagation"] +@flaky(1735812000) def test_metric_request_tainted(telemetry_writer): with override_env(dict(DD_IAST_TELEMETRY_VERBOSITY="INFORMATION")), override_global_config( dict(_iast_enabled=True) @@ -103,7 +105,7 @@ def test_metric_request_tainted(telemetry_writer): metrics_result = telemetry_writer._namespace._metrics_data generate_metrics = metrics_result[TELEMETRY_TYPE_GENERATE_METRICS][TELEMETRY_NAMESPACE_TAG_IAST] - assert len(generate_metrics) == 2, "Expected 1 generate_metrics" + assert len(generate_metrics) == 2, "Expected 2 generate_metrics" assert [metric.name for metric in generate_metrics.values()] == ["executed.source", "request.tainted"] assert span.get_metric(IAST_SPAN_TAGS.TELEMETRY_REQUEST_TAINTED) > 0 From 40b3fb7805501bd739f5fd4850d22522ecbe746c Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Thu, 9 May 2024 14:17:47 +0200 Subject: [PATCH 018/104] fix(iast): improve header injection patching (#9210) In some cases, django patching doesn't work for header injection due to a circular import: https://github.com/DataDog/system-tests/pull/2437 ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. - [x] If change touches code that signs or publishes builds or packages, or handles credentials of any kind, I've requested a review from `@DataDog/security-design-and-guidance`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../_iast/taint_sinks/header_injection.py | 54 +++++++------------ 1 file changed, 19 insertions(+), 35 deletions(-) diff --git a/ddtrace/appsec/_iast/taint_sinks/header_injection.py b/ddtrace/appsec/_iast/taint_sinks/header_injection.py index 7fa0b6111dd..8f53416db36 100644 --- a/ddtrace/appsec/_iast/taint_sinks/header_injection.py +++ b/ddtrace/appsec/_iast/taint_sinks/header_injection.py @@ -1,5 +1,7 @@ +from ddtrace.contrib import trace_utils from ddtrace.internal.logger import get_logger from ddtrace.settings.asm import config as asm_config +from ddtrace.vendor.wrapt.importer import when_imported from ..._common_module_patches import try_unwrap from ..._constants import IAST_SPAN_TAGS @@ -8,7 +10,6 @@ from .._metrics import increment_iast_span_metric from .._patch import set_and_check_module_is_patched from .._patch import set_module_unpatched -from .._patch import try_wrap_function_wrapper from ..constants import HEADER_NAME_VALUE_SEPARATOR from ..constants import VULN_HEADER_INJECTION from ..processor import AppSecIastSpanProcessor @@ -32,40 +33,23 @@ def patch(): if not set_and_check_module_is_patched("django", default_attr="_datadog_header_injection_patch"): return - try_wrap_function_wrapper( - "wsgiref.headers", - "Headers.add_header", - _iast_h, - ) - try_wrap_function_wrapper( - "wsgiref.headers", - "Headers.__setitem__", - _iast_h, - ) - try_wrap_function_wrapper( - "werkzeug.datastructures", - "Headers.set", - _iast_h, - ) - try_wrap_function_wrapper( - "werkzeug.datastructures", - "Headers.add", - _iast_h, - ) - - # Django - try_wrap_function_wrapper( - "django.http.response", - "HttpResponseBase.__setitem__", - _iast_h, - ) - try_wrap_function_wrapper( - "django.http.response", - "ResponseHeaders.__setitem__", - _iast_h, - ) - - _set_metric_iast_instrumented_sink(VULN_HEADER_INJECTION, 1) + @when_imported("wsgiref.headers") + def _(m): + trace_utils.wrap(m, "Headers.add_header", _iast_h) + trace_utils.wrap(m, "Headers.__setitem__", _iast_h) + + @when_imported("werkzeug.datastructures") + def _(m): + trace_utils.wrap(m, "Headers.add", _iast_h) + trace_utils.wrap(m, "Headers.set", _iast_h) + + @when_imported("django.http.response") + def _(m): + trace_utils.wrap(m, "HttpResponse.__setitem__", _iast_h) + trace_utils.wrap(m, "ResponseHeaders.__setitem__", _iast_h) + trace_utils.wrap(m, "HttpResponseBase.__setitem__", _iast_h) + + _set_metric_iast_instrumented_sink(VULN_HEADER_INJECTION) def unpatch(): From 567b7132be57f1f59b150d1cf1a1ab57fd763f0a Mon Sep 17 00:00:00 2001 From: Federico Mon Date: Thu, 9 May 2024 15:04:15 +0200 Subject: [PATCH 019/104] chore(internal): add ``time_window`` parameter to rate limiter (#9206) ## Description This pull request introduces a new feature to the rate limiter module by adding a time_window parameter. The end goal is to make the rate limiter configurable, allowing for rate limiting at different time periods such as 1 minute, 1 hour, or the default of 1 second. ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Co-authored-by: Alberto Vara --- ddtrace/internal/rate_limiter.py | 38 ++++++++-------- tests/tracer/test_rate_limiter.py | 72 +++++++++++++++++-------------- 2 files changed, 57 insertions(+), 53 deletions(-) diff --git a/ddtrace/internal/rate_limiter.py b/ddtrace/internal/rate_limiter.py index e06493f8ca5..abf2be72fc6 100644 --- a/ddtrace/internal/rate_limiter.py +++ b/ddtrace/internal/rate_limiter.py @@ -20,6 +20,7 @@ class RateLimiter(object): __slots__ = ( "_lock", "current_window_ns", + "time_window", "last_update_ns", "max_tokens", "prev_window_rate", @@ -29,8 +30,7 @@ class RateLimiter(object): "tokens_total", ) - def __init__(self, rate_limit): - # type: (int) -> None + def __init__(self, rate_limit: int, time_window: float = 1e9): """ Constructor for RateLimiter @@ -39,8 +39,11 @@ def __init__(self, rate_limit): rate limit == 0 to disallow all requests, rate limit < 0 to allow all requests :type rate_limit: :obj:`int` + :param time_window: The time window where the rate limit applies in nanoseconds. default value is 1 second. + :type time_window: :obj:`float` """ self.rate_limit = rate_limit + self.time_window = time_window self.tokens = rate_limit # type: float self.max_tokens = rate_limit @@ -57,8 +60,7 @@ def __init__(self, rate_limit): def _has_been_configured(self): return self.rate_limit != DEFAULT_SAMPLING_RATE_LIMIT - def is_allowed(self, timestamp_ns): - # type: (int) -> bool + def is_allowed(self, timestamp_ns: int) -> bool: """ Check whether the current request is allowed or not @@ -74,15 +76,15 @@ def is_allowed(self, timestamp_ns): self._update_rate_counts(allowed, timestamp_ns) return allowed - def _update_rate_counts(self, allowed, timestamp_ns): - # type: (bool, int) -> None + def _update_rate_counts(self, allowed: bool, timestamp_ns: int) -> None: # No tokens have been seen yet, start a new window if not self.current_window_ns: self.current_window_ns = timestamp_ns - # If more than 1 second has past since last window, reset + # If more time than the configured time window + # has past since last window, reset # DEV: We are comparing nanoseconds, so 1e9 is 1 second - elif timestamp_ns - self.current_window_ns >= 1e9: + elif timestamp_ns - self.current_window_ns >= self.time_window: # Store previous window's rate to average with current for `.effective_rate` self.prev_window_rate = self._current_window_rate() self.tokens_allowed = 0 @@ -94,8 +96,7 @@ def _update_rate_counts(self, allowed, timestamp_ns): self.tokens_allowed += 1 self.tokens_total += 1 - def _is_allowed(self, timestamp_ns): - # type: (int) -> bool + def _is_allowed(self, timestamp_ns: int) -> bool: # Rate limit of 0 blocks everything if self.rate_limit == 0: return False @@ -114,8 +115,7 @@ def _is_allowed(self, timestamp_ns): return False - def _replenish(self, timestamp_ns): - # type: (int) -> None + def _replenish(self, timestamp_ns: int) -> None: try: # If we are at the max, we do not need to add any more if self.tokens == self.max_tokens: @@ -123,7 +123,7 @@ def _replenish(self, timestamp_ns): # Add more available tokens based on how much time has passed # DEV: We store as nanoseconds, convert to seconds - elapsed = (timestamp_ns - self.last_update_ns) / 1e9 + elapsed = (timestamp_ns - self.last_update_ns) / self.time_window finally: # always update the timestamp # we can't update at the beginning of the function, since if we did, our calculation for @@ -136,8 +136,7 @@ def _replenish(self, timestamp_ns): self.tokens + (elapsed * self.rate_limit), ) - def _current_window_rate(self): - # type: () -> float + def _current_window_rate(self) -> float: # No tokens have been seen, effectively 100% sample rate # DEV: This is to avoid division by zero error if not self.tokens_total: @@ -147,8 +146,7 @@ def _current_window_rate(self): return self.tokens_allowed / self.tokens_total @property - def effective_rate(self): - # type: () -> float + def effective_rate(self) -> float: """ Return the effective sample rate of this rate limiter @@ -220,8 +218,7 @@ def __attrs_post_init__(self): self.budget = self.max_budget = 1.0 self._on_exceed_called = False - def limit(self, f=None, *args, **kwargs): - # type: (Optional[Callable[..., Any]], *Any, **Any) -> Any + def limit(self, f: Optional[Callable[..., Any]] = None, *args: Any, **kwargs: Any) -> Any: """Make rate-limited calls to a function with the given arguments.""" should_call = False with self._lock: @@ -249,8 +246,7 @@ def limit(self, f=None, *args, **kwargs): else: return RateLimitExceeded - def __call__(self, f): - # type: (Callable[..., Any]) -> Callable[..., Any] + def __call__(self, f: Callable[..., Any]) -> Callable[..., Any]: def limited_f(*args, **kwargs): return self.limit(f, *args, **kwargs) diff --git a/tests/tracer/test_rate_limiter.py b/tests/tracer/test_rate_limiter.py index 0171e3b2bf8..d076ac6f6b3 100644 --- a/tests/tracer/test_rate_limiter.py +++ b/tests/tracer/test_rate_limiter.py @@ -9,46 +9,50 @@ from ddtrace.internal.rate_limiter import RateLimitExceeded -def nanoseconds(x): - # Helper to iterate over x seconds in nanosecond steps - return range(0, int(1e9 * x), int(1e9)) +def nanoseconds(x, time_window): + # Helper to iterate over x time windows in nanosecond steps + return range(0, int(time_window * x), int(time_window)) -def test_rate_limiter_init(): - limiter = RateLimiter(rate_limit=100) +@pytest.mark.parametrize("time_window", [1e3, 1e6, 1e9]) +def test_rate_limiter_init(time_window): + limiter = RateLimiter(rate_limit=100, time_window=time_window) assert limiter.rate_limit == 100 assert limiter.tokens == 100 assert limiter.max_tokens == 100 assert limiter.last_update_ns <= compat.monotonic_ns() -def test_rate_limiter_rate_limit_0(): - limiter = RateLimiter(rate_limit=0) +@pytest.mark.parametrize("time_window", [1e3, 1e6, 1e9]) +def test_rate_limiter_rate_limit_0(time_window): + limiter = RateLimiter(rate_limit=0, time_window=time_window) assert limiter.rate_limit == 0 assert limiter.tokens == 0 assert limiter.max_tokens == 0 now_ns = compat.monotonic_ns() - for i in nanoseconds(10000): + for i in nanoseconds(10000, time_window): # Make sure the time is different for every check assert limiter.is_allowed(now_ns + i) is False -def test_rate_limiter_rate_limit_negative(): - limiter = RateLimiter(rate_limit=-1) +@pytest.mark.parametrize("time_window", [1e3, 1e6, 1e9]) +def test_rate_limiter_rate_limit_negative(time_window): + limiter = RateLimiter(rate_limit=-1, time_window=time_window) assert limiter.rate_limit == -1 assert limiter.tokens == -1 assert limiter.max_tokens == -1 now_ns = compat.monotonic_ns() - for i in nanoseconds(10000): + for i in nanoseconds(10000, time_window): # Make sure the time is different for every check assert limiter.is_allowed(now_ns + i) is True @pytest.mark.parametrize("rate_limit", [1, 10, 50, 100, 500, 1000]) -def test_rate_limiter_is_allowed(rate_limit): - limiter = RateLimiter(rate_limit=rate_limit) +@pytest.mark.parametrize("time_window", [1e3, 1e6, 1e9]) +def test_rate_limiter_is_allowed(rate_limit, time_window): + limiter = RateLimiter(rate_limit=rate_limit, time_window=time_window) def check_limit(time_ns): # Up to the allowed limit is allowed @@ -63,13 +67,14 @@ def check_limit(time_ns): now = compat.monotonic_ns() # Check the limit for 5 time frames - for i in nanoseconds(5): + for i in nanoseconds(5, time_window): # Keep the same timeframe check_limit(now + i) -def test_rate_limiter_is_allowed_large_gap(): - limiter = RateLimiter(rate_limit=100) +@pytest.mark.parametrize("time_window", [1e3, 1e6, 1e9]) +def test_rate_limiter_is_allowed_large_gap(time_window): + limiter = RateLimiter(rate_limit=100, time_window=time_window) # Start time now_ns = compat.monotonic_ns() @@ -79,25 +84,27 @@ def test_rate_limiter_is_allowed_large_gap(): # Large gap before next call to `is_allowed()` for _ in range(100): - assert limiter.is_allowed(now_ns + (1e9 * 100)) is True + assert limiter.is_allowed(now_ns + (time_window * 100)) is True -def test_rate_limiter_is_allowed_small_gaps(): - limiter = RateLimiter(rate_limit=100) +@pytest.mark.parametrize("time_window", [1e3, 1e6, 1e9]) +def test_rate_limiter_is_allowed_small_gaps(time_window): + limiter = RateLimiter(rate_limit=100, time_window=time_window) # Start time now_ns = compat.monotonic_ns() gap = 1e9 / 100 # Keep incrementing by a gap to keep us at our rate limit - for i in nanoseconds(10000): + for i in nanoseconds(10000, time_window): # Keep the same timeframe time_ns = now_ns + (gap * i) assert limiter.is_allowed(time_ns) is True -def test_rate_liimter_effective_rate_rates(): - limiter = RateLimiter(rate_limit=100) +@pytest.mark.parametrize("time_window", [1e3, 1e6, 1e9]) +def test_rate_liimter_effective_rate_rates(time_window): + limiter = RateLimiter(rate_limit=100, time_window=time_window) # Static rate limit window starting_window_ns = compat.monotonic_ns() @@ -113,7 +120,7 @@ def test_rate_liimter_effective_rate_rates(): assert limiter.current_window_ns == starting_window_ns prev_rate = 0.5 - window_ns = starting_window_ns + 1e9 + window_ns = starting_window_ns + time_window for _ in range(100): assert limiter.is_allowed(window_ns) is True @@ -127,8 +134,9 @@ def test_rate_liimter_effective_rate_rates(): assert limiter.current_window_ns == window_ns -def test_rate_limiter_effective_rate_starting_rate(): - limiter = RateLimiter(rate_limit=1) +@pytest.mark.parametrize("time_window", [1e3, 1e6, 1e9]) +def test_rate_limiter_effective_rate_starting_rate(time_window): + limiter = RateLimiter(rate_limit=1, time_window=time_window) now_ns = compat.monotonic_ns() @@ -148,7 +156,7 @@ def test_rate_limiter_effective_rate_starting_rate(): assert limiter.prev_window_rate is None # Gap of 0.9999 seconds, same window - time_ns = now_ns + (0.9999 * 1e9) + time_ns = now_ns + (0.9999 * time_window) assert limiter.is_allowed(time_ns) is False # DEV: We have rate_limit=1 set assert limiter.effective_rate == 0.5 @@ -156,24 +164,24 @@ def test_rate_limiter_effective_rate_starting_rate(): assert limiter.prev_window_rate is None # Gap of 1.0 seconds, new window - time_ns = now_ns + 1e9 + time_ns = now_ns + time_window assert limiter.is_allowed(time_ns) is True assert limiter.effective_rate == 0.75 - assert limiter.current_window_ns == (now_ns + 1e9) + assert limiter.current_window_ns == (now_ns + time_window) assert limiter.prev_window_rate == 0.5 # Gap of 1.9999 seconds, same window - time_ns = now_ns + (1.9999 * 1e9) + time_ns = now_ns + (1.9999 * time_window) assert limiter.is_allowed(time_ns) is False assert limiter.effective_rate == 0.5 - assert limiter.current_window_ns == (now_ns + 1e9) # Same as old window + assert limiter.current_window_ns == (now_ns + time_window) # Same as old window assert limiter.prev_window_rate == 0.5 # Large gap of 100 seconds, new window - time_ns = now_ns + (100.0 * 1e9) + time_ns = now_ns + (100.0 * time_window) assert limiter.is_allowed(time_ns) is True assert limiter.effective_rate == 0.75 - assert limiter.current_window_ns == (now_ns + (100.0 * 1e9)) + assert limiter.current_window_ns == (now_ns + (100.0 * time_window)) assert limiter.prev_window_rate == 0.5 From 41622139b1175c6b104e39ce70646a42b351df5e Mon Sep 17 00:00:00 2001 From: Juanjo Alvarez Martinez Date: Thu, 9 May 2024 16:41:43 +0200 Subject: [PATCH 020/104] flaky telemetry test again (#9216) ## Checklist - [ ] Change(s) are motivated and described in the PR description - [ ] Testing strategy is described if automated tests are not included in the PR - [ ] Risks are described (performance impact, potential for breakage, maintainability) - [ ] Change is maintainable (easy to change, telemetry, documentation) - [ ] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [ ] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [ ] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [ ] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [ ] Title is accurate - [ ] All changes are related to the pull request's stated goal - [ ] Description motivates each change - [ ] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [ ] Testing strategy adequately addresses listed risks - [ ] Change is maintainable (easy to change, telemetry, documentation) - [ ] Release note makes sense to a user of the library - [ ] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [ ] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Signed-off-by: Juanjo Alvarez --- tests/appsec/iast/test_telemetry.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/appsec/iast/test_telemetry.py b/tests/appsec/iast/test_telemetry.py index 7fec5330dff..51978730594 100644 --- a/tests/appsec/iast/test_telemetry.py +++ b/tests/appsec/iast/test_telemetry.py @@ -75,6 +75,7 @@ def test_metric_executed_sink(telemetry_writer): assert span.get_metric(IAST_SPAN_TAGS.TELEMETRY_REQUEST_TAINTED) is None +@flaky(1735812000) def test_metric_instrumented_propagation(telemetry_writer): with override_env(dict(DD_IAST_TELEMETRY_VERBOSITY="INFORMATION")), override_global_config( dict(_iast_enabled=True) From a0a8330203390bf8160fb84b76a7864a49b569df Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Thu, 9 May 2024 17:23:42 +0200 Subject: [PATCH 021/104] chore(iast): remove deprecated dependency (#9212) After IAST redaction refactor (https://github.com/DataDog/dd-trace-py/pull/9163 and https://github.com/DataDog/dd-trace-py/pull/9126) `sqlparse` dependency is deprecated ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. - [x] If change touches code that signs or publishes builds or packages, or handles credentials of any kind, I've requested a review from `@DataDog/security-design-and-guidance`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../requirements/{60a424f.txt => 1c9c221.txt} | 28 ++++++++++--------- hatch.toml | 1 - pyproject.toml | 1 - riotfile.py | 1 - 4 files changed, 15 insertions(+), 16 deletions(-) rename .riot/requirements/{60a424f.txt => 1c9c221.txt} (70%) diff --git a/.riot/requirements/60a424f.txt b/.riot/requirements/1c9c221.txt similarity index 70% rename from .riot/requirements/60a424f.txt rename to .riot/requirements/1c9c221.txt index 1b6cd80153c..2853883012c 100644 --- a/.riot/requirements/60a424f.txt +++ b/.riot/requirements/1c9c221.txt @@ -2,53 +2,55 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --no-annotate .riot/requirements/60a424f.in +# pip-compile --no-annotate .riot/requirements/1c9c221.in # aiosqlite==0.17.0 +annotated-types==0.6.0 attrs==23.2.0 -blinker==1.7.0 +blinker==1.8.2 bytecode==0.15.1 cattrs==22.2.0 certifi==2024.2.2 charset-normalizer==3.3.2 click==8.1.7 -coverage[toml]==7.4.4 +coverage[toml]==7.5.1 ddsketch==3.0.1 deprecated==1.2.14 envier==0.5.1 flask==3.0.3 greenlet==3.0.3 hypothesis==6.45.0 -idna==3.6 +idna==3.7 importlib-metadata==7.0.0 iniconfig==2.0.0 iso8601==1.1.0 -itsdangerous==2.1.2 -jinja2==3.1.3 +itsdangerous==2.2.0 +jinja2==3.1.4 markupsafe==2.1.5 mock==5.1.0 opentelemetry-api==1.24.0 opentracing==2.4.0 packaging==24.0 -peewee==3.17.1 -pluggy==1.4.0 +peewee==3.17.3 +pluggy==1.5.0 pony==0.7.17 protobuf==5.26.1 pycryptodome==3.20.0 +pydantic==2.7.1 +pydantic-core==2.18.2 pypika-tortoise==0.1.6 -pytest==8.1.1 +pytest==8.2.0 pytest-cov==5.0.0 pytest-mock==3.14.0 pytz==2024.1 requests==2.31.0 six==1.16.0 sortedcontainers==2.4.0 -sqlalchemy==2.0.29 -sqlparse==0.4.4 -tortoise-orm==0.20.0 +sqlalchemy==2.0.30 +tortoise-orm==0.20.1 typing-extensions==4.11.0 urllib3==2.2.1 -werkzeug==3.0.2 +werkzeug==3.0.3 wrapt==1.16.0 xmltodict==0.13.0 zipp==3.18.1 diff --git a/hatch.toml b/hatch.toml index f3a99c4babb..4d821289944 100644 --- a/hatch.toml +++ b/hatch.toml @@ -79,7 +79,6 @@ dependencies = [ # copied from library dependencies "opentracing>=2.0.0", "bytecode", "six>=1.12.0", - "sqlparse>=0.2.2", ] extra-dependencies = [ "reno[sphinx]~=3.5.0", diff --git a/pyproject.toml b/pyproject.toml index f841fdd0e17..91d13676096 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,6 @@ dependencies = [ "six>=1.12.0", "typing_extensions", "xmltodict>=0.12", - "sqlparse>=0.2.2", ] [project.optional-dependencies] diff --git a/riotfile.py b/riotfile.py index 24e1f5244a2..c5143c26777 100644 --- a/riotfile.py +++ b/riotfile.py @@ -211,7 +211,6 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): "opentelemetry-api": ">=1", "opentracing": ">=2.0.0", "bytecode": latest, - "sqlparse": ">=0.2.2", }, env={ "DD_CIVISIBILITY_ITR_ENABLED": "0", From 15a123f00d506ca0108b1d07388223df150e24e2 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Thu, 9 May 2024 18:53:20 +0200 Subject: [PATCH 022/104] chore(iast): type hinting (#9217) ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. - [x] If change touches code that signs or publishes builds or packages, or handles credentials of any kind, I've requested a review from `@DataDog/security-design-and-guidance`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/appsec/_iast/__init__.py | 1 - ddtrace/appsec/_iast/_ast/__init__.py | 1 - ddtrace/appsec/_iast/_ast/ast_patching.py | 32 +++---- ddtrace/appsec/_iast/_ast/visitor.py | 93 ++++++++++--------- ddtrace/appsec/_iast/_metrics.py | 4 +- .../appsec/_iast/_overhead_control_engine.py | 38 +++----- ddtrace/appsec/_iast/_patch.py | 16 +--- .../appsec/_iast/_patches/json_tainting.py | 9 +- .../appsec/_iast/_taint_tracking/aspects.py | 6 +- ddtrace/appsec/_iast/_utils.py | 5 +- ddtrace/appsec/_iast/processor.py | 17 +--- ddtrace/appsec/_iast/taint_sinks/ast_taint.py | 18 ++-- .../_iast/taint_sinks/header_injection.py | 6 +- .../_iast/taint_sinks/insecure_cookie.py | 10 +- .../_iast/taint_sinks/path_traversal.py | 6 +- .../appsec/_iast/taint_sinks/weak_cipher.py | 45 ++++----- ddtrace/appsec/_iast/taint_sinks/weak_hash.py | 34 +++---- tests/appsec/iast/iast_utils.py | 6 +- 18 files changed, 147 insertions(+), 200 deletions(-) diff --git a/ddtrace/appsec/_iast/__init__.py b/ddtrace/appsec/_iast/__init__.py index eed11574e68..e7f73adf313 100644 --- a/ddtrace/appsec/_iast/__init__.py +++ b/ddtrace/appsec/_iast/__init__.py @@ -22,7 +22,6 @@ class MyVulnerability(VulnerabilityBase): @WeakHash.wrap def wrapped_function(wrapped, instance, args, kwargs): - # type: (Callable, str, Any, Any, Any) -> Any WeakHash.report( evidence_value=evidence, ) diff --git a/ddtrace/appsec/_iast/_ast/__init__.py b/ddtrace/appsec/_iast/_ast/__init__.py index e5a0d9b4834..e69de29bb2d 100644 --- a/ddtrace/appsec/_iast/_ast/__init__.py +++ b/ddtrace/appsec/_iast/_ast/__init__.py @@ -1 +0,0 @@ -#!/usr/bin/env python3 diff --git a/ddtrace/appsec/_iast/_ast/ast_patching.py b/ddtrace/appsec/_iast/_ast/ast_patching.py index 032fcb90551..a73ca7f4405 100644 --- a/ddtrace/appsec/_iast/_ast/ast_patching.py +++ b/ddtrace/appsec/_iast/_ast/ast_patching.py @@ -6,13 +6,11 @@ import re from sys import builtin_module_names from types import ModuleType -from typing import TYPE_CHECKING # noqa:F401 +from typing import Optional +from typing import Set +from typing import Text from typing import Tuple - -if TYPE_CHECKING: - from typing import Optional # noqa:F401 - from ddtrace.appsec._constants import IAST from ddtrace.appsec._python_info.stdlib import _stdlib_for_python_version from ddtrace.internal.logger import get_logger @@ -22,8 +20,8 @@ # Prefixes for modules where IAST patching is allowed -IAST_ALLOWLIST = ("tests.appsec.iast",) # type: tuple[str, ...] -IAST_DENYLIST = ( +IAST_ALLOWLIST: Tuple[Text, ...] = ("tests.appsec.iast",) +IAST_DENYLIST: Tuple[Text, ...] = ( "ddtrace", "pkg_resources", "encodings", # this package is used to load encodings when a module is imported, propagation is not needed @@ -32,7 +30,7 @@ "Crypto", # This module is patched by the IAST patch methods, propagation is not needed "api_pb2", # Patching crashes with these auto-generated modules, propagation is not needed "api_pb2_grpc", # ditto -) # type: tuple[str, ...] +) if IAST.PATCH_MODULES in os.environ: @@ -47,7 +45,7 @@ log = get_logger(__name__) -def get_encoding(module_path): # type: (str) -> str +def get_encoding(module_path: Text) -> Text: """ First tries to detect the encoding for the file, otherwise, returns global encoding default @@ -67,7 +65,7 @@ def get_encoding(module_path): # type: (str) -> str import importlib_metadata as il_md # type: ignore[no-redef] -def _build_installed_package_names_list(): # type: (...) -> set[str] +def _build_installed_package_names_list() -> Set[Text]: return { ilmd_d.metadata["name"] for ilmd_d in il_md.distributions() if ilmd_d is not None and ilmd_d.files is not None } @@ -78,11 +76,11 @@ def _build_installed_package_names_list(): # type: (...) -> set[str] ) -def _in_python_stdlib_or_third_party(module_name): # type: (str) -> bool +def _in_python_stdlib_or_third_party(module_name: str) -> bool: return module_name.split(".")[0].lower() in [x.lower() for x in _NOT_PATCH_MODULE_NAMES] -def _should_iast_patch(module_name): # type: (str) -> bool +def _should_iast_patch(module_name: Text) -> bool: """ select if module_name should be patch from the longuest prefix that match in allow or deny list. if a prefix is in both list, deny is selected. @@ -94,10 +92,10 @@ def _should_iast_patch(module_name): # type: (str) -> bool def visit_ast( - source_text, # type: str - module_path, # type: str - module_name="", # type: str -): # type: (...) -> Optional[str] + source_text: Text, + module_path: Text, + module_name: Text = "", +) -> Optional[str]: parsed_ast = ast.parse(source_text, module_path) visitor = AstVisitor( @@ -116,7 +114,7 @@ def visit_ast( _FLASK_INSTANCE_REGEXP = re.compile(r"(\S*)\s*=.*Flask\(.*") -def _remove_flask_run(text): # type (str) -> str +def _remove_flask_run(text: Text) -> Text: """ Find and remove flask app.run() call. This is used for patching the app.py file and exec'ing to replace the module without creating diff --git a/ddtrace/appsec/_iast/_ast/visitor.py b/ddtrace/appsec/_iast/_ast/visitor.py index c726b7570b4..003c4e3a465 100644 --- a/ddtrace/appsec/_iast/_ast/visitor.py +++ b/ddtrace/appsec/_iast/_ast/visitor.py @@ -5,9 +5,10 @@ import copy import os import sys -from typing import Any # noqa:F401 -from typing import List # noqa:F401 -from typing import Set # noqa:F401 +from typing import Any +from typing import List +from typing import Set +from typing import Text from .._metrics import _set_metric_iast_instrumented_propagation from ..constants import DEFAULT_PATH_TRAVERSAL_FUNCTIONS @@ -199,7 +200,8 @@ def _merge_taint_sinks(*args_functions: Set[str]) -> Set[str]: return merged_set - def _is_string_node(self, node): # type: (Any) -> bool + @staticmethod + def _is_string_node(node: Any) -> bool: if PY30_37 and isinstance(node, ast.Bytes): return True @@ -208,7 +210,8 @@ def _is_string_node(self, node): # type: (Any) -> bool return False - def _is_numeric_node(self, node): # type: (Any) -> bool + @staticmethod + def _is_numeric_node(node: Any) -> bool: if PY30_37 and isinstance(node, ast.Num): return True @@ -217,10 +220,20 @@ def _is_numeric_node(self, node): # type: (Any) -> bool return False - def _is_node_constant_or_binop(self, node): # type: (Any) -> bool + @staticmethod + def _get_function_name(call_node: ast.Call, is_function: bool) -> Text: + if is_function: + return call_node.func.id # type: ignore[attr-defined] + # If the call is to a method + elif type(call_node.func) == ast.Name: + return call_node.func.id + + return call_node.func.attr # type: ignore[attr-defined] + + def _is_node_constant_or_binop(self, node: Any) -> bool: return self._is_string_node(node) or self._is_numeric_node(node) or isinstance(node, ast.BinOp) - def _is_call_excluded(self, func_name_node): # type: (str) -> bool + def _is_call_excluded(self, func_name_node: Text) -> bool: if not self.excluded_functions: return False excluded_for_caller = self.excluded_functions.get(func_name_node, tuple()) + self.excluded_functions.get( @@ -228,8 +241,7 @@ def _is_call_excluded(self, func_name_node): # type: (str) -> bool ) return "" in excluded_for_caller or self._current_function_name in excluded_for_caller - def _is_string_format_with_literals(self, call_node): - # type: (ast.Call) -> bool + def _is_string_format_with_literals(self, call_node: ast.Call) -> bool: return ( self._is_string_node(call_node.func.value) # type: ignore[attr-defined] and call_node.func.attr == "format" # type: ignore[attr-defined] @@ -237,16 +249,7 @@ def _is_string_format_with_literals(self, call_node): and all(map(lambda x: self._is_node_constant_or_binop(x.value), call_node.keywords)) ) - def _get_function_name(self, call_node, is_function): # type: (ast.Call, bool) -> str - if is_function: - return call_node.func.id # type: ignore[attr-defined] - # If the call is to a method - elif type(call_node.func) == ast.Name: - return call_node.func.id - - return call_node.func.attr # type: ignore[attr-defined] - - def _should_replace_with_taint_sink(self, call_node, is_function): # type: (ast.Call, bool) -> bool + def _should_replace_with_taint_sink(self, call_node: ast.Call, is_function: bool) -> bool: function_name = self._get_function_name(call_node, is_function) if function_name in self._taint_sink_replace_disabled: @@ -254,7 +257,7 @@ def _should_replace_with_taint_sink(self, call_node, is_function): # type: (ast return any(allowed in function_name for allowed in self._taint_sink_replace_any) - def _add_original_function_as_arg(self, call_node, is_function): # type: (ast.Call, bool) -> Any + def _add_original_function_as_arg(self, call_node: ast.Call, is_function: bool) -> Any: """ Creates the arguments for the original function """ @@ -273,8 +276,8 @@ def _add_original_function_as_arg(self, call_node, is_function): # type: (ast.C return new_args - def _node(self, type_, pos_from_node, **kwargs): - # type: (Any, Any, Any) -> Any + @staticmethod + def _node(type_: Any, pos_from_node: Any, **kwargs: Any) -> Any: """ Abstract some basic differences in node structure between versions """ @@ -295,8 +298,7 @@ def _node(self, type_, pos_from_node, **kwargs): lineno=lineno, end_lineno=end_lineno, col_offset=col_offset, end_col_offset=end_col_offset, **kwargs ) - def _name_node(self, from_node, _id, ctx=ast.Load()): # noqa: B008 - # type: (Any, str, Any) -> ast.Name + def _name_node(self, from_node: Any, _id: Text, ctx: Any = ast.Load()) -> ast.Name: # noqa: B008 return self._node( ast.Name, from_node, @@ -304,8 +306,7 @@ def _name_node(self, from_node, _id, ctx=ast.Load()): # noqa: B008 ctx=ctx, ) - def _attr_node(self, from_node, attr, ctx=ast.Load()): # noqa: B008 - # type: (Any, str, Any) -> ast.Name + def _attr_node(self, from_node: Any, attr: Text, ctx: Any = ast.Load()) -> ast.Name: # noqa: B008 attr_attr = "" name_attr = "" if attr: @@ -317,7 +318,7 @@ def _attr_node(self, from_node, attr, ctx=ast.Load()): # noqa: B008 name_node = self._name_node(from_node, name_attr, ctx=ctx) return self._node(ast.Attribute, from_node, attr=attr_attr, ctx=ctx, value=name_node) - def _assign_node(self, from_node, targets, value): # type: (Any, List[Any], Any) -> Any + def _assign_node(self, from_node: Any, targets: List[Any], value: Any) -> Any: return self._node( ast.Assign, from_node, @@ -326,7 +327,8 @@ def _assign_node(self, from_node, targets, value): # type: (Any, List[Any], Any type_comment=None, ) - def find_insert_position(self, module_node): # type: (ast.Module) -> int + @staticmethod + def find_insert_position(module_node: ast.Module) -> int: insert_position = 0 from_future_import_found = False import_found = False @@ -359,8 +361,8 @@ def find_insert_position(self, module_node): # type: (ast.Module) -> int return insert_position - def _none_constant(self, from_node, ctx=ast.Load()): # noqa: B008 - # type: (Any, Any) -> Any + @staticmethod + def _none_constant(from_node: Any) -> Any: # noqa: B008 if PY30_37: return ast.NameConstant(lineno=from_node.lineno, col_offset=from_node.col_offset, value=None) @@ -374,7 +376,8 @@ def _none_constant(self, from_node, ctx=ast.Load()): # noqa: B008 kind=None, ) - def _int_constant(self, from_node, value): + @staticmethod + def _int_constant(from_node, value): return ast.Constant( lineno=from_node.lineno, col_offset=from_node.col_offset, @@ -384,11 +387,10 @@ def _int_constant(self, from_node, value): kind=None, ) - def _call_node(self, from_node, func, args): # type: (Any, Any, List[Any]) -> Any + def _call_node(self, from_node: Any, func: Any, args: List[Any]) -> Any: return self._node(ast.Call, from_node, func=func, args=args, keywords=[]) - def visit_Module(self, module_node): - # type: (ast.Module) -> Any + def visit_Module(self, module_node: ast.Module) -> Any: """ Insert the import statement for the replacements module """ @@ -428,8 +430,7 @@ def visit_Module(self, module_node): self.generic_visit(module_node) return module_node - def visit_FunctionDef(self, def_node): - # type: (ast.FunctionDef) -> Any + def visit_FunctionDef(self, def_node: ast.FunctionDef) -> Any: """ Special case for some tests which would enter in a patching loop otherwise when visiting the check functions @@ -466,7 +467,7 @@ def visit_FunctionDef(self, def_node): return def_node - def visit_Call(self, call_node): # type: (ast.Call) -> Any + def visit_Call(self, call_node: ast.Call) -> Any: """ Replace a call or method """ @@ -579,7 +580,7 @@ def visit_Call(self, call_node): # type: (ast.Call) -> Any return call_node - def visit_BinOp(self, call_node): # type: (ast.BinOp) -> Any + def visit_BinOp(self, call_node: ast.BinOp) -> Any: """ Replace a binary operator """ @@ -595,7 +596,7 @@ def visit_BinOp(self, call_node): # type: (ast.BinOp) -> Any return call_node - def visit_FormattedValue(self, fmt_value_node): # type: (ast.FormattedValue) -> Any + def visit_FormattedValue(self, fmt_value_node: ast.FormattedValue) -> Any: """ Visit a FormattedValue node which are the constituent atoms for the JoinedStr which are used to implement f-strings. @@ -626,7 +627,7 @@ def visit_FormattedValue(self, fmt_value_node): # type: (ast.FormattedValue) -> _set_metric_iast_instrumented_propagation() return call_node - def visit_JoinedStr(self, joinedstr_node): # type: (ast.JoinedStr) -> Any + def visit_JoinedStr(self, joinedstr_node: ast.JoinedStr) -> Any: """ Replaced the JoinedStr AST node with a Call to the replacement function. Most of the work inside fstring is done by visit_FormattedValue above. @@ -656,7 +657,7 @@ def visit_JoinedStr(self, joinedstr_node): # type: (ast.JoinedStr) -> Any _set_metric_iast_instrumented_propagation() return call_node - def visit_AugAssign(self, augassign_node): # type: (ast.AugAssign) -> Any + def visit_AugAssign(self, augassign_node: ast.AugAssign) -> Any: """Replace an inplace add or multiply.""" if isinstance(augassign_node.target, ast.Subscript): # Can't augassign to function call, ignore this node @@ -667,7 +668,7 @@ def visit_AugAssign(self, augassign_node): # type: (ast.AugAssign) -> Any # TODO: Replace an inplace add or multiply (+= / *=) return augassign_node - def visit_Assign(self, assign_node): # type: (ast.Assign) -> Any + def visit_Assign(self, assign_node: ast.Assign) -> Any: """ Add the ignore marks for left-side subscripts or list/tuples to avoid problems later with the visit_Subscript node. @@ -704,7 +705,7 @@ def visit_Assign(self, assign_node): # type: (ast.Assign) -> Any self.generic_visit(assign_node) return assign_node - def visit_Delete(self, assign_node): # type: (ast.Delete) -> Any + def visit_Delete(self, assign_node: ast.Delete) -> Any: # del replaced_index(foo, bar) would fail so avoid converting the right hand side # since it's going to be deleted anyway @@ -715,21 +716,21 @@ def visit_Delete(self, assign_node): # type: (ast.Delete) -> Any self.generic_visit(assign_node) return assign_node - def visit_AnnAssign(self, node): # type: (ast.AnnAssign) -> Any + def visit_AnnAssign(self, node: ast.AnnAssign) -> Any: # AnnAssign is a type annotation, we don't need to convert it # and we avoid converting any subscript inside it. _mark_avoid_convert_recursively(node) self.generic_visit(node) return node - def visit_ClassDef(self, node): # type: (ast.ClassDef) -> Any + def visit_ClassDef(self, node: ast.ClassDef) -> Any: for i in node.bases: _mark_avoid_convert_recursively(i) self.generic_visit(node) return node - def visit_Subscript(self, subscr_node): # type: (ast.Subscript) -> Any + def visit_Subscript(self, subscr_node: ast.Subscript) -> Any: """ Turn an indexes[1] and slices[0:1:2] into the replacement function call Optimization: dont convert if the indexes are strings diff --git a/ddtrace/appsec/_iast/_metrics.py b/ddtrace/appsec/_iast/_metrics.py index 041efe91dd1..521b5e5d79d 100644 --- a/ddtrace/appsec/_iast/_metrics.py +++ b/ddtrace/appsec/_iast/_metrics.py @@ -2,6 +2,7 @@ import sys import traceback from typing import Dict +from typing import Text from ddtrace.appsec._constants import IAST from ddtrace.appsec._constants import IAST_SPAN_TAGS @@ -56,8 +57,7 @@ def wrapper(f): @metric_verbosity(TELEMETRY_MANDATORY_VERBOSITY) @deduplication -def _set_iast_error_metric(msg): - # type: (str) -> None +def _set_iast_error_metric(msg: Text) -> None: # Due to format_exc and format_exception returns the error and the last frame try: exception_type, exception_instance, _traceback_list = sys.exc_info() diff --git a/ddtrace/appsec/_iast/_overhead_control_engine.py b/ddtrace/appsec/_iast/_overhead_control_engine.py index 252d2398176..d02015c81b5 100644 --- a/ddtrace/appsec/_iast/_overhead_control_engine.py +++ b/ddtrace/appsec/_iast/_overhead_control_engine.py @@ -5,23 +5,20 @@ """ import os import threading -from typing import TYPE_CHECKING # noqa:F401 +from typing import Set +from typing import Text +from typing import Tuple +from typing import Type +from ddtrace._trace.span import Span from ddtrace.internal.logger import get_logger from ddtrace.sampler import RateSampler -if TYPE_CHECKING: # pragma: no cover - from typing import Set # noqa:F401 - from typing import Tuple # noqa:F401 - from typing import Type # noqa:F401 - - from ddtrace._trace.span import Span # noqa:F401 - log = get_logger(__name__) -def get_request_sampling_value(): # type: () -> float +def get_request_sampling_value() -> float: # Percentage of requests analyzed by IAST (default: 30%) return float(os.environ.get("DD_IAST_REQUEST_SAMPLING", 30.0)) @@ -37,7 +34,7 @@ class Operation(object): _lock = threading.Lock() _vulnerability_quota = MAX_VULNERABILITIES_PER_REQUEST - _reported_vulnerabilities = set() # type: Set[Tuple[str, int]] + _reported_vulnerabilities: Set[Tuple[str, int]] = set() @classmethod def reset(cls): @@ -45,8 +42,7 @@ def reset(cls): cls._reported_vulnerabilities = set() @classmethod - def acquire_quota(cls): - # type: () -> bool + def acquire_quota(cls) -> bool: cls._lock.acquire() result = False if cls._vulnerability_quota > 0: @@ -56,8 +52,7 @@ def acquire_quota(cls): return result @classmethod - def increment_quota(cls): - # type: () -> bool + def increment_quota(cls) -> bool: cls._lock.acquire() result = False if cls._vulnerability_quota < MAX_VULNERABILITIES_PER_REQUEST: @@ -67,16 +62,14 @@ def increment_quota(cls): return result @classmethod - def has_quota(cls): - # type: () -> bool + def has_quota(cls) -> bool: cls._lock.acquire() result = cls._vulnerability_quota > 0 cls._lock.release() return result @classmethod - def is_not_reported(cls, filename, lineno): - # type: (str, int) -> bool + def is_not_reported(cls, filename: Text, lineno: int) -> bool: vulnerability_id = (filename, lineno) if vulnerability_id in cls._reported_vulnerabilities: return False @@ -92,14 +85,13 @@ class OverheadControl(object): _lock = threading.Lock() _request_quota = MAX_REQUESTS - _vulnerabilities = set() # type: Set[Type[Operation]] + _vulnerabilities: Set[Type[Operation]] = set() _sampler = RateSampler(sample_rate=get_request_sampling_value() / 100.0) def reconfigure(self): self._sampler = RateSampler(sample_rate=get_request_sampling_value() / 100.0) - def acquire_request(self, span): - # type: (Span) -> bool + def acquire_request(self, span: Span) -> bool: """Decide whether if IAST analysis will be done for this request. - Block a request's quota at start of the request to limit simultaneous requests analyzed. - Use sample rating to analyze only a percentage of the total requests (30% by default). @@ -121,13 +113,11 @@ def release_request(self): self._request_quota += 1 self.vulnerabilities_reset_quota() - def register(self, klass): - # type: (Type[Operation]) -> Type[Operation] + def register(self, klass: Type[Operation]) -> Type[Operation]: """Register vulnerabilities/taint_sinks. This set of elements will restart for each request.""" self._vulnerabilities.add(klass) return klass def vulnerabilities_reset_quota(self): - # type: () -> None for k in self._vulnerabilities: k.reset() diff --git a/ddtrace/appsec/_iast/_patch.py b/ddtrace/appsec/_iast/_patch.py index 6f197023202..37ce3e9334d 100644 --- a/ddtrace/appsec/_iast/_patch.py +++ b/ddtrace/appsec/_iast/_patch.py @@ -1,10 +1,7 @@ import sys -from typing import Any # noqa:F401 -from typing import Callable # noqa:F401 -from typing import Dict # noqa:F401 -from typing import Optional # noqa:F401 +from typing import Callable +from typing import Text -from ddtrace.appsec._common_module_patches import try_unwrap # noqa:F401 from ddtrace.appsec._common_module_patches import wrap_object from ddtrace.internal.logger import get_logger from ddtrace.vendor.wrapt import FunctionWrapper @@ -15,8 +12,7 @@ log = get_logger(__name__) -def set_and_check_module_is_patched(module_str, default_attr="_datadog_patch"): - # type: (str, str) -> Optional[bool] +def set_and_check_module_is_patched(module_str: Text, default_attr: Text = "_datadog_patch") -> bool: try: __import__(module_str) module = sys.modules[module_str] @@ -28,8 +24,7 @@ def set_and_check_module_is_patched(module_str, default_attr="_datadog_patch"): return True -def set_module_unpatched(module_str, default_attr="_datadog_patch"): - # type: (str, str) -> None +def set_module_unpatched(module_str: Text, default_attr: Text = "_datadog_patch"): try: __import__(module_str) module = sys.modules[module_str] @@ -38,8 +33,7 @@ def set_module_unpatched(module_str, default_attr="_datadog_patch"): pass -def try_wrap_function_wrapper(module, name, wrapper): - # type: (str, str, Callable) -> None +def try_wrap_function_wrapper(module: Text, name: Text, wrapper: Callable): try: wrap_object(module, name, FunctionWrapper, (wrapper,)) except (ImportError, AttributeError): diff --git a/ddtrace/appsec/_iast/_patches/json_tainting.py b/ddtrace/appsec/_iast/_patches/json_tainting.py index 97755260e63..7bd297a492a 100644 --- a/ddtrace/appsec/_iast/_patches/json_tainting.py +++ b/ddtrace/appsec/_iast/_patches/json_tainting.py @@ -1,9 +1,11 @@ +from typing import Text + +from ddtrace.appsec._common_module_patches import try_unwrap from ddtrace.internal.logger import get_logger from ddtrace.settings.asm import config as asm_config from .._patch import set_and_check_module_is_patched from .._patch import set_module_unpatched -from .._patch import try_unwrap from .._patch import try_wrap_function_wrapper @@ -13,13 +15,11 @@ _DEFAULT_ATTR = "_datadog_json_tainting_patch" -def get_version(): - # type: () -> str +def get_version() -> Text: return "" def unpatch_iast(): - # type: () -> None set_module_unpatched("json", default_attr=_DEFAULT_ATTR) try_unwrap("json", "loads") if asm_config._iast_lazy_taint: @@ -28,7 +28,6 @@ def unpatch_iast(): def patch(): - # type: () -> None """Wrap functions which interact with file system.""" if not set_and_check_module_is_patched("json", default_attr=_DEFAULT_ATTR): return diff --git a/ddtrace/appsec/_iast/_taint_tracking/aspects.py b/ddtrace/appsec/_iast/_taint_tracking/aspects.py index 3c0465884ce..843f324dcae 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/aspects.py +++ b/ddtrace/appsec/_iast/_taint_tracking/aspects.py @@ -367,7 +367,7 @@ def zfill_aspect( prefix = candidate_text[0] in ("-", "+") difflen = len(result) - len(candidate_text) - ranges_new = [] # type: List[TaintRange] + ranges_new: List[TaintRange] = [] ranges_new_append = ranges_new.append ranges_new_extend = ranges_new.extend @@ -402,7 +402,7 @@ def format_aspect( if not args: return orig_function(*args, **kwargs) - candidate_text = args[0] # type: str + candidate_text: Text = args[0] args = args[flag_added_args:] result = candidate_text.format(*args, **kwargs) @@ -433,7 +433,7 @@ def format_map_aspect( if orig_function and not args: return orig_function(*args, **kwargs) - candidate_text = args[0] # type: str + candidate_text: Text = args[0] args = args[flag_added_args:] if not isinstance(candidate_text, IAST.TEXT_TYPES): return candidate_text.format_map(*args, **kwargs) diff --git a/ddtrace/appsec/_iast/_utils.py b/ddtrace/appsec/_iast/_utils.py index c1b72f28f04..5ceb5741ba5 100644 --- a/ddtrace/appsec/_iast/_utils.py +++ b/ddtrace/appsec/_iast/_utils.py @@ -1,11 +1,12 @@ import sys from typing import List +from typing import Text from ddtrace.internal.logger import get_logger from ddtrace.settings.asm import config as asm_config -def _is_python_version_supported(): # type: () -> bool +def _is_python_version_supported() -> bool: # IAST supports Python versions 3.6 to 3.12 return (3, 6, 0) <= sys.version_info < (3, 13, 0) @@ -31,7 +32,7 @@ def _get_source_index(sources: List, source) -> int: return -1 -def _get_patched_code(module_path, module_name): # type: (str, str) -> str +def _get_patched_code(module_path: Text, module_name: Text) -> str: """ Print the patched code to stdout, for debugging purposes. """ diff --git a/ddtrace/appsec/_iast/processor.py b/ddtrace/appsec/_iast/processor.py index 8d0adffdb90..eb232c498b7 100644 --- a/ddtrace/appsec/_iast/processor.py +++ b/ddtrace/appsec/_iast/processor.py @@ -1,8 +1,9 @@ -from typing import TYPE_CHECKING +from typing import Optional import attr from ddtrace._trace.processor import SpanProcessor +from ddtrace._trace.span import Span from ddtrace.appsec._constants import APPSEC from ddtrace.appsec._constants import IAST from ddtrace.constants import ORIGIN_KEY @@ -19,19 +20,13 @@ from .reporter import IastSpanReporter -if TYPE_CHECKING: # pragma: no cover - from typing import Optional # noqa:F401 - - from ddtrace._trace.span import Span # noqa:F401 - log = get_logger(__name__) @attr.s(eq=False) class AppSecIastSpanProcessor(SpanProcessor): @staticmethod - def is_span_analyzed(span=None): - # type: (Optional[Span]) -> bool + def is_span_analyzed(span: Optional[Span] = None) -> bool: if span is None: from ddtrace import tracer @@ -41,8 +36,7 @@ def is_span_analyzed(span=None): return True return False - def on_span_start(self, span): - # type: (Span) -> None + def on_span_start(self, span: Span): if span.span_type != SpanTypes.WEB: return @@ -59,8 +53,7 @@ def on_span_start(self, span): core.set_item(IAST.REQUEST_IAST_ENABLED, request_iast_enabled, span=span) - def on_span_finish(self, span): - # type: (Span) -> None + def on_span_finish(self, span: Span): """Report reported vulnerabilities. Span Tags: diff --git a/ddtrace/appsec/_iast/taint_sinks/ast_taint.py b/ddtrace/appsec/_iast/taint_sinks/ast_taint.py index 57d22f63796..46dec1bbd93 100644 --- a/ddtrace/appsec/_iast/taint_sinks/ast_taint.py +++ b/ddtrace/appsec/_iast/taint_sinks/ast_taint.py @@ -1,4 +1,5 @@ -from typing import TYPE_CHECKING # noqa:F401 +from typing import Any +from typing import Callable from ..._constants import IAST_SPAN_TAGS from .._metrics import _set_metric_iast_executed_sink @@ -9,18 +10,13 @@ from .weak_randomness import WeakRandomness -if TYPE_CHECKING: - from typing import Any # noqa:F401 - from typing import Callable # noqa:F401 - - # TODO: we also need a native version of this function! def ast_function( - func, # type: Callable - flag_added_args, # type: Any - *args, # type: Any - **kwargs, # type: Any -): # type: (...) -> Any + func: Callable, + flag_added_args: Any, + *args: Any, + **kwargs: Any, +) -> Any: instance = getattr(func, "__self__", None) func_name = getattr(func, "__name__", None) cls_name = "" diff --git a/ddtrace/appsec/_iast/taint_sinks/header_injection.py b/ddtrace/appsec/_iast/taint_sinks/header_injection.py index 8f53416db36..77f146caf6a 100644 --- a/ddtrace/appsec/_iast/taint_sinks/header_injection.py +++ b/ddtrace/appsec/_iast/taint_sinks/header_injection.py @@ -1,3 +1,5 @@ +from typing import Text + from ddtrace.contrib import trace_utils from ddtrace.internal.logger import get_logger from ddtrace.settings.asm import config as asm_config @@ -19,8 +21,7 @@ log = get_logger(__name__) -def get_version(): - # type: () -> str +def get_version() -> Text: return "" @@ -53,7 +54,6 @@ def _(m): def unpatch(): - # type: () -> None try_unwrap("wsgiref.headers", "Headers.add_header") try_unwrap("wsgiref.headers", "Headers.__setitem__") try_unwrap("werkzeug.datastructures", "Headers.set") diff --git a/ddtrace/appsec/_iast/taint_sinks/insecure_cookie.py b/ddtrace/appsec/_iast/taint_sinks/insecure_cookie.py index e188e2ecbe4..b2e151a666b 100644 --- a/ddtrace/appsec/_iast/taint_sinks/insecure_cookie.py +++ b/ddtrace/appsec/_iast/taint_sinks/insecure_cookie.py @@ -1,4 +1,5 @@ -from typing import TYPE_CHECKING # noqa:F401 +from typing import Dict +from typing import Optional from ..._constants import IAST_SPAN_TAGS from .. import oce @@ -10,11 +11,6 @@ from ..taint_sinks._base import VulnerabilityBase -if TYPE_CHECKING: - from typing import Dict # noqa:F401 - from typing import Optional # noqa:F401 - - @oce.register class InsecureCookie(VulnerabilityBase): vulnerability_type = VULN_INSECURE_COOKIE @@ -34,7 +30,7 @@ class NoSameSite(VulnerabilityBase): skip_location = True -def asm_check_cookies(cookies): # type: (Optional[Dict[str, str]]) -> None +def asm_check_cookies(cookies: Optional[Dict[str, str]]) -> None: if not cookies: return diff --git a/ddtrace/appsec/_iast/taint_sinks/path_traversal.py b/ddtrace/appsec/_iast/taint_sinks/path_traversal.py index e6fde3b40e2..1c51eb273cf 100644 --- a/ddtrace/appsec/_iast/taint_sinks/path_traversal.py +++ b/ddtrace/appsec/_iast/taint_sinks/path_traversal.py @@ -1,4 +1,5 @@ from typing import Any +from typing import Text from ddtrace.internal.logger import get_logger @@ -21,18 +22,15 @@ class PathTraversal(VulnerabilityBase): vulnerability_type = VULN_PATH_TRAVERSAL -def get_version(): - # type: () -> str +def get_version() -> Text: return "" def unpatch_iast(): - # type: () -> None set_module_unpatched("builtins", default_attr="_datadog_path_traversal_patch") def patch(): - # type: () -> None """Wrap functions which interact with file system.""" if not set_and_check_module_is_patched("builtins", default_attr="_datadog_path_traversal_patch"): return diff --git a/ddtrace/appsec/_iast/taint_sinks/weak_cipher.py b/ddtrace/appsec/_iast/taint_sinks/weak_cipher.py index 21a494edf3b..f57a4dd3cf1 100644 --- a/ddtrace/appsec/_iast/taint_sinks/weak_cipher.py +++ b/ddtrace/appsec/_iast/taint_sinks/weak_cipher.py @@ -1,36 +1,33 @@ import os -from typing import TYPE_CHECKING # noqa:F401 - +from typing import Any +from typing import Callable +from typing import Set +from typing import Text + +from ddtrace.appsec._common_module_patches import try_unwrap +from ddtrace.appsec._constants import IAST_SPAN_TAGS +from ddtrace.appsec._iast.constants import BLOWFISH_DEF +from ddtrace.appsec._iast.constants import DEFAULT_WEAK_CIPHER_ALGORITHMS +from ddtrace.appsec._iast.constants import DES_DEF +from ddtrace.appsec._iast.constants import RC2_DEF +from ddtrace.appsec._iast.constants import RC4_DEF +from ddtrace.appsec._iast.constants import VULN_WEAK_CIPHER_TYPE from ddtrace.internal.logger import get_logger -from ..._constants import IAST_SPAN_TAGS from .. import oce from .._metrics import _set_metric_iast_executed_sink from .._metrics import _set_metric_iast_instrumented_sink from .._metrics import increment_iast_span_metric from .._patch import set_and_check_module_is_patched from .._patch import set_module_unpatched -from .._patch import try_unwrap from .._patch import try_wrap_function_wrapper -from ..constants import BLOWFISH_DEF -from ..constants import DEFAULT_WEAK_CIPHER_ALGORITHMS -from ..constants import DES_DEF -from ..constants import RC2_DEF -from ..constants import RC4_DEF -from ..constants import VULN_WEAK_CIPHER_TYPE from ._base import VulnerabilityBase -if TYPE_CHECKING: # pragma: no cover - from typing import Any # noqa:F401 - from typing import Callable # noqa:F401 - from typing import Set # noqa:F401 - log = get_logger(__name__) -def get_weak_cipher_algorithms(): - # type: () -> Set +def get_weak_cipher_algorithms() -> Set: CONFIGURED_WEAK_CIPHER_ALGORITHMS = None DD_IAST_WEAK_CIPHER_ALGORITHMS = os.getenv("DD_IAST_WEAK_CIPHER_ALGORITHMS") if DD_IAST_WEAK_CIPHER_ALGORITHMS: @@ -46,7 +43,6 @@ class WeakCipher(VulnerabilityBase): def unpatch_iast(): - # type: () -> None set_module_unpatched("Crypto", default_attr="_datadog_weak_cipher_patch") set_module_unpatched("cryptography", default_attr="_datadog_weak_cipher_patch") @@ -60,13 +56,11 @@ def unpatch_iast(): try_unwrap("cryptography.hazmat.primitives.ciphers", "Cipher.encryptor") -def get_version(): - # type: () -> str +def get_version() -> Text: return "" def patch(): - # type: () -> None """Wrap hashing functions. Weak hashing algorithms are those that have been proven to be of high risk, or even completely broken, and thus are not fit for use. @@ -127,8 +121,7 @@ def wrapped_aux_blowfish_function(wrapped, instance, args, kwargs): @WeakCipher.wrap -def wrapped_rc4_function(wrapped, instance, args, kwargs): - # type: (Callable, Any, Any, Any) -> Any +def wrapped_rc4_function(wrapped: Callable, instance: Any, args: Any, kwargs: Any) -> Any: increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, WeakCipher.vulnerability_type) _set_metric_iast_executed_sink(WeakCipher.vulnerability_type) WeakCipher.report( @@ -138,8 +131,7 @@ def wrapped_rc4_function(wrapped, instance, args, kwargs): @WeakCipher.wrap -def wrapped_function(wrapped, instance, args, kwargs): - # type: (Callable, Any, Any, Any) -> Any +def wrapped_function(wrapped: Callable, instance: Any, args: Any, kwargs: Any) -> Any: if hasattr(instance, "_dd_weakcipher_algorithm"): evidence = instance._dd_weakcipher_algorithm + "_" + str(instance.__class__.__name__) increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, WeakCipher.vulnerability_type) @@ -152,8 +144,7 @@ def wrapped_function(wrapped, instance, args, kwargs): @WeakCipher.wrap -def wrapped_cryptography_function(wrapped, instance, args, kwargs): - # type: (Callable, Any, Any, Any) -> Any +def wrapped_cryptography_function(wrapped: Callable, instance: Any, args: Any, kwargs: Any) -> Any: algorithm_name = instance.algorithm.name.lower() if algorithm_name in get_weak_cipher_algorithms(): increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, WeakCipher.vulnerability_type) diff --git a/ddtrace/appsec/_iast/taint_sinks/weak_hash.py b/ddtrace/appsec/_iast/taint_sinks/weak_hash.py index 0932cc9fb05..e5ead3aed29 100644 --- a/ddtrace/appsec/_iast/taint_sinks/weak_hash.py +++ b/ddtrace/appsec/_iast/taint_sinks/weak_hash.py @@ -1,7 +1,12 @@ import os import sys from typing import TYPE_CHECKING # noqa:F401 +from typing import Any +from typing import Callable +from typing import Set +from typing import Text # noqa:F401 +from ddtrace.appsec._common_module_patches import try_unwrap from ddtrace.internal.logger import get_logger from ..._constants import IAST_SPAN_TAGS @@ -11,7 +16,6 @@ from .._metrics import increment_iast_span_metric from .._patch import set_and_check_module_is_patched from .._patch import set_module_unpatched -from .._patch import try_unwrap from .._patch import try_wrap_function_wrapper from ..constants import DEFAULT_WEAK_HASH_ALGORITHMS from ..constants import MD5_DEF @@ -20,16 +24,10 @@ from ._base import VulnerabilityBase -if TYPE_CHECKING: # pragma: no cover - from typing import Any # noqa:F401 - from typing import Callable # noqa:F401 - from typing import Set # noqa:F401 - log = get_logger(__name__) -def get_weak_hash_algorithms(): - # type: () -> Set +def get_weak_hash_algorithms() -> Set: CONFIGURED_WEAK_HASH_ALGORITHMS = None DD_IAST_WEAK_HASH_ALGORITHMS = os.getenv("DD_IAST_WEAK_HASH_ALGORITHMS") if DD_IAST_WEAK_HASH_ALGORITHMS: @@ -44,7 +42,6 @@ class WeakHash(VulnerabilityBase): def unpatch_iast(): - # type: () -> None set_module_unpatched("hashlib", default_attr="_datadog_weak_hash_patch") set_module_unpatched("Crypto", default_attr="_datadog_weak_hash_patch") @@ -67,13 +64,11 @@ def unpatch_iast(): try_unwrap("Crypto.Hash.SHA1", "SHA1Hash.hexdigest") -def get_version(): - # type: () -> str +def get_version() -> Text: return "" def patch(): - # type: () -> None """Wrap hashing functions. Weak hashing algorithms are those that have been proven to be of high risk, or even completely broken, and thus are not fit for use. @@ -124,8 +119,7 @@ def patch(): @WeakHash.wrap -def wrapped_digest_function(wrapped, instance, args, kwargs): - # type: (Callable, Any, Any, Any) -> Any +def wrapped_digest_function(wrapped: Callable, instance: Any, args: Any, kwargs: Any) -> Any: if instance.name.lower() in get_weak_hash_algorithms(): increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, WeakHash.vulnerability_type) _set_metric_iast_executed_sink(WeakHash.vulnerability_type) @@ -136,20 +130,17 @@ def wrapped_digest_function(wrapped, instance, args, kwargs): @WeakHash.wrap -def wrapped_md5_function(wrapped, instance, args, kwargs): - # type: (Callable, Any, Any, Any) -> Any +def wrapped_md5_function(wrapped: Callable, instance: Any, args: Any, kwargs: Any) -> Any: return wrapped_function(wrapped, MD5_DEF, instance, args, kwargs) @WeakHash.wrap -def wrapped_sha1_function(wrapped, instance, args, kwargs): - # type: (Callable, Any, Any, Any) -> Any +def wrapped_sha1_function(wrapped: Callable, instance: Any, args: Any, kwargs: Any) -> Any: return wrapped_function(wrapped, SHA1_DEF, instance, args, kwargs) @WeakHash.wrap -def wrapped_new_function(wrapped, instance, args, kwargs): - # type: (Callable, Any, Any, Any) -> Any +def wrapped_new_function(wrapped: Callable, instance: Any, args: Any, kwargs: Any) -> Any: if args[0].lower() in get_weak_hash_algorithms(): increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, WeakHash.vulnerability_type) _set_metric_iast_executed_sink(WeakHash.vulnerability_type) @@ -159,8 +150,7 @@ def wrapped_new_function(wrapped, instance, args, kwargs): return wrapped(*args, **kwargs) -def wrapped_function(wrapped, evidence, instance, args, kwargs): - # type: (Callable, str, Any, Any, Any) -> Any +def wrapped_function(wrapped: Callable, evidence: Text, instance: Any, args: Any, kwargs: Any) -> Any: increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, WeakHash.vulnerability_type) _set_metric_iast_executed_sink(WeakHash.vulnerability_type) WeakHash.report( diff --git a/tests/appsec/iast/iast_utils.py b/tests/appsec/iast/iast_utils.py index b6a1a83fde9..1ea7b66f81e 100644 --- a/tests/appsec/iast/iast_utils.py +++ b/tests/appsec/iast/iast_utils.py @@ -1,8 +1,10 @@ import re +from typing import Optional +from typing import Text import zlib -def get_line(label, filename=None): +def get_line(label: Text, filename: Optional[Text] = None): """get the line number after the label comment in source file `filename`""" with open(filename, "r") as file_in: for nb_line, line in enumerate(file_in): @@ -11,7 +13,7 @@ def get_line(label, filename=None): raise AssertionError("label %s not found" % label) -def get_line_and_hash(label, vuln_type, filename=None, fixed_line=None): +def get_line_and_hash(label: Text, vuln_type: Text, filename=None, fixed_line=None): """return the line number and the associated vulnerability hash for `label` and source file `filename`""" if fixed_line is not None: From 6e1d2da79b2b9ef92785d2214ddf9db6636ba665 Mon Sep 17 00:00:00 2001 From: Juanjo Alvarez Martinez Date: Thu, 9 May 2024 19:26:14 +0200 Subject: [PATCH 023/104] chore: fix telemetry flakyness (#9218) ## Checklist - [X] Change(s) are motivated and described in the PR description - [X] Testing strategy is described if automated tests are not included in the PR - [X] Risks are described (performance impact, potential for breakage, maintainability) - [X] Change is maintainable (easy to change, telemetry, documentation) - [X] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [X] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [X] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [X] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [ ] Title is accurate - [ ] All changes are related to the pull request's stated goal - [ ] Description motivates each change - [ ] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [ ] Testing strategy adequately addresses listed risks - [ ] Change is maintainable (easy to change, telemetry, documentation) - [ ] Release note makes sense to a user of the library - [ ] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [ ] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Signed-off-by: Juanjo Alvarez --- tests/appsec/iast/conftest.py | 12 ++++++++++++ tests/appsec/iast/test_telemetry.py | 13 +++++-------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/tests/appsec/iast/conftest.py b/tests/appsec/iast/conftest.py index cc304eb56b7..fb2da25326b 100644 --- a/tests/appsec/iast/conftest.py +++ b/tests/appsec/iast/conftest.py @@ -29,6 +29,18 @@ from ddtrace.appsec._iast._taint_tracking import reset_context +@pytest.fixture +def no_request_sampling(tracer): + with override_env( + { + "DD_IAST_REQUEST_SAMPLING": "100", + "DD_IAST_MAX_CONCURRENT_REQUEST": "100", + } + ): + oce.reconfigure() + yield + + def iast_span(tracer, env, request_sampling="100", deduplication=False): try: from ddtrace.contrib.langchain.patch import patch as langchain_patch diff --git a/tests/appsec/iast/test_telemetry.py b/tests/appsec/iast/test_telemetry.py index 51978730594..2fef0b1855a 100644 --- a/tests/appsec/iast/test_telemetry.py +++ b/tests/appsec/iast/test_telemetry.py @@ -17,7 +17,6 @@ from ddtrace.internal.telemetry.constants import TELEMETRY_TYPE_GENERATE_METRICS from tests.appsec.iast.aspects.conftest import _iast_patched_module from tests.utils import DummyTracer -from tests.utils import flaky from tests.utils import override_env from tests.utils import override_global_config @@ -44,7 +43,7 @@ def test_metric_verbosity(lvl, env_lvl, expected_result): assert metric_verbosity(lvl)(lambda: 1)() == expected_result -def test_metric_executed_sink(telemetry_writer): +def test_metric_executed_sink(no_request_sampling, telemetry_writer): with override_env(dict(DD_IAST_TELEMETRY_VERBOSITY="INFORMATION")), override_global_config( dict(_iast_enabled=True) ): @@ -75,8 +74,7 @@ def test_metric_executed_sink(telemetry_writer): assert span.get_metric(IAST_SPAN_TAGS.TELEMETRY_REQUEST_TAINTED) is None -@flaky(1735812000) -def test_metric_instrumented_propagation(telemetry_writer): +def test_metric_instrumented_propagation(no_request_sampling, telemetry_writer): with override_env(dict(DD_IAST_TELEMETRY_VERBOSITY="INFORMATION")), override_global_config( dict(_iast_enabled=True) ): @@ -84,12 +82,11 @@ def test_metric_instrumented_propagation(telemetry_writer): metrics_result = telemetry_writer._namespace._metrics_data generate_metrics = metrics_result[TELEMETRY_TYPE_GENERATE_METRICS][TELEMETRY_NAMESPACE_TAG_IAST] - assert len(generate_metrics) == 1, "Expected 1 generate_metrics" assert [metric.name for metric in generate_metrics.values()] == ["instrumented.propagation"] + assert len(generate_metrics) == 1, "Expected 1 generate_metrics" -@flaky(1735812000) -def test_metric_request_tainted(telemetry_writer): +def test_metric_request_tainted(no_request_sampling, telemetry_writer): with override_env(dict(DD_IAST_TELEMETRY_VERBOSITY="INFORMATION")), override_global_config( dict(_iast_enabled=True) ): @@ -106,8 +103,8 @@ def test_metric_request_tainted(telemetry_writer): metrics_result = telemetry_writer._namespace._metrics_data generate_metrics = metrics_result[TELEMETRY_TYPE_GENERATE_METRICS][TELEMETRY_NAMESPACE_TAG_IAST] - assert len(generate_metrics) == 2, "Expected 2 generate_metrics" assert [metric.name for metric in generate_metrics.values()] == ["executed.source", "request.tainted"] + assert len(generate_metrics) == 2, "Expected 2 generate_metrics" assert span.get_metric(IAST_SPAN_TAGS.TELEMETRY_REQUEST_TAINTED) > 0 From b2840ab9d31ef49fdb85d1e71a2b27414bc5148d Mon Sep 17 00:00:00 2001 From: Federico Mon Date: Thu, 9 May 2024 20:19:37 +0200 Subject: [PATCH 024/104] fix(iast): avoid crashing grpc patch if MessageMapContainer import fails (#9215) Code Security: Fix an issue at GRPC patching where if `google._upb` module wasn't available, an unhandled ImportError would be raised. ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Signed-off-by: Juanjo Alvarez Co-authored-by: Juanjo Alvarez Co-authored-by: Alberto Vara Co-authored-by: Brett Langdon --- ddtrace/appsec/_handlers.py | 26 ++++++++++++------- ...prc-import-not-crash-e13c48dcbcc1985e.yaml | 4 +++ tests/appsec/iast/test_grpc_iast.py | 17 ++++++++++++ 3 files changed, 38 insertions(+), 9 deletions(-) create mode 100644 releasenotes/notes/asm-gprc-import-not-crash-e13c48dcbcc1985e.yaml diff --git a/ddtrace/appsec/_handlers.py b/ddtrace/appsec/_handlers.py index e5326f54165..a679b817522 100644 --- a/ddtrace/appsec/_handlers.py +++ b/ddtrace/appsec/_handlers.py @@ -1,3 +1,4 @@ +from collections.abc import MutableMapping import functools import io import json @@ -19,6 +20,13 @@ from ddtrace.vendor.wrapt import wrap_function_wrapper as _w +MessageMapContainer = None +try: + from google._upb._message import MessageMapContainer # type: ignore[no-redef] +except ImportError: + pass + + log = get_logger(__name__) _BODY_METHODS = {"POST", "PUT", "DELETE", "PATCH"} @@ -359,10 +367,6 @@ def _on_django_patch(): def _custom_protobuf_getattribute(self, name): - from collections.abc import MutableMapping - - from google._upb._message import MessageMapContainer - from ddtrace.appsec._iast._taint_tracking import taint_pyobject from ddtrace.appsec._iast._taint_tracking._native.taint_tracking import OriginType from ddtrace.appsec._iast._taint_utils import taint_structure @@ -375,7 +379,7 @@ def _custom_protobuf_getattribute(self, name): source_value=ret, source_origin=OriginType.GRPC_BODY, ) - elif isinstance(ret, MutableMapping): + elif MessageMapContainer is not None and isinstance(ret, MutableMapping): if isinstance(ret, MessageMapContainer) and len(ret): # Patch the message-values class first_key = next(iter(ret)) @@ -398,10 +402,14 @@ def _patch_protobuf_class(cls): return if not hasattr(getattr_method, "__datadog_custom"): - # Replace the class __getattribute__ method with our custom one - # (replacement is done at the class level because it would incur on a recursive loop with the instance) - cls.__saved_getattr = getattr_method - cls.__getattribute__ = _custom_protobuf_getattribute + try: + # Replace the class __getattribute__ method with our custom one + # (replacement is done at the class level because it would incur on a recursive loop with the instance) + cls.__saved_getattr = getattr_method + cls.__getattribute__ = _custom_protobuf_getattribute + except TypeError: + # Avoid failing on Python 3.12 while patching immutable types + pass def _on_grpc_response(response): diff --git a/releasenotes/notes/asm-gprc-import-not-crash-e13c48dcbcc1985e.yaml b/releasenotes/notes/asm-gprc-import-not-crash-e13c48dcbcc1985e.yaml new file mode 100644 index 00000000000..aa02945302a --- /dev/null +++ b/releasenotes/notes/asm-gprc-import-not-crash-e13c48dcbcc1985e.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + Code Security: Avoid an ``ImportError`` at patching stage if ``google._upb`` module is not available. diff --git a/tests/appsec/iast/test_grpc_iast.py b/tests/appsec/iast/test_grpc_iast.py index f41d79f85e7..bb30476d909 100644 --- a/tests/appsec/iast/test_grpc_iast.py +++ b/tests/appsec/iast/test_grpc_iast.py @@ -1,5 +1,6 @@ import grpc from grpc._grpcio_metadata import __version__ as _GRPC_VERSION +import mock from tests.contrib.grpc.common import GrpcBaseTestCase from tests.contrib.grpc.hello_pb2 import HelloRequest @@ -72,3 +73,19 @@ def test_taint_iast_last(self): res = stub1.SayHelloLast(requests_iterator) assert hasattr(res, "message") _check_test_range(res.message) + + def test_taint_iast_patching_import_error(self): + with mock.patch.dict("sys.modules", {"google._upb._message": None}), override_env({"DD_IAST_ENABLED": "True"}): + from collections import UserDict + + from ddtrace.appsec._handlers import _custom_protobuf_getattribute + from ddtrace.appsec._handlers import _patch_protobuf_class + + class MyUserDict(UserDict): + pass + + _patch_protobuf_class(MyUserDict) + original_dict = {"apple": 1, "banana": 2} + mutable_mapping = MyUserDict(original_dict) + + _custom_protobuf_getattribute(mutable_mapping, "data") From 772627a225ceb0791edb83bdf6c9e7bb7b51f5d5 Mon Sep 17 00:00:00 2001 From: Juanjo Alvarez Martinez Date: Fri, 10 May 2024 12:24:58 +0200 Subject: [PATCH 025/104] chore: more robust telemetry tests, immune to some internal sink points side effects (#9220) ## Description Some sink points that are very commonly called, like `http.client` could be internally while executing telemetry test. This makes these tests check only the required sinks or vulnerabilities. This should remove flakyness from those tests. ## Checklist - [X] Change(s) are motivated and described in the PR description - [X] Testing strategy is described if automated tests are not included in the PR - [X] Risks are described (performance impact, potential for breakage, maintainability) - [X] Change is maintainable (easy to change, telemetry, documentation) - [X] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [X] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [X] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [X] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Signed-off-by: Juanjo Alvarez --- tests/appsec/iast/test_telemetry.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/tests/appsec/iast/test_telemetry.py b/tests/appsec/iast/test_telemetry.py index 2fef0b1855a..c36ac3ad44c 100644 --- a/tests/appsec/iast/test_telemetry.py +++ b/tests/appsec/iast/test_telemetry.py @@ -64,11 +64,12 @@ def test_metric_executed_sink(no_request_sampling, telemetry_writer): metrics_result = telemetry_writer._namespace._metrics_data - generate_metrics = metrics_result[TELEMETRY_TYPE_GENERATE_METRICS][TELEMETRY_NAMESPACE_TAG_IAST] - assert len(generate_metrics) == 1, "Expected 1 generate_metrics" - assert [metric.name for metric in generate_metrics.values()] == [ - "executed.sink", - ] + generate_metrics = metrics_result[TELEMETRY_TYPE_GENERATE_METRICS][TELEMETRY_NAMESPACE_TAG_IAST].values() + assert len(generate_metrics) >= 1 + # Remove potential sinks from internal usage of the lib (like http.client, used to communicate with + # the agent) + filtered_metrics = [metric for metric in generate_metrics if metric._tags[0] == ("vulnerability_type", "WEAK_HASH")] + assert [metric._tags for metric in filtered_metrics] == [(("vulnerability_type", "WEAK_HASH"),)] assert span.get_metric("_dd.iast.telemetry.executed.sink.weak_hash") > 0 # request.tainted metric is None because AST is not running in this test assert span.get_metric(IAST_SPAN_TAGS.TELEMETRY_REQUEST_TAINTED) is None @@ -82,8 +83,10 @@ def test_metric_instrumented_propagation(no_request_sampling, telemetry_writer): metrics_result = telemetry_writer._namespace._metrics_data generate_metrics = metrics_result[TELEMETRY_TYPE_GENERATE_METRICS][TELEMETRY_NAMESPACE_TAG_IAST] - assert [metric.name for metric in generate_metrics.values()] == ["instrumented.propagation"] - assert len(generate_metrics) == 1, "Expected 1 generate_metrics" + # Remove potential sinks from internal usage of the lib (like http.client, used to communicate with + # the agent) + filtered_metrics = [metric.name for metric in generate_metrics.values() if metric.name != "executed.sink"] + assert filtered_metrics == ["instrumented.propagation"] def test_metric_request_tainted(no_request_sampling, telemetry_writer): @@ -103,8 +106,11 @@ def test_metric_request_tainted(no_request_sampling, telemetry_writer): metrics_result = telemetry_writer._namespace._metrics_data generate_metrics = metrics_result[TELEMETRY_TYPE_GENERATE_METRICS][TELEMETRY_NAMESPACE_TAG_IAST] - assert [metric.name for metric in generate_metrics.values()] == ["executed.source", "request.tainted"] - assert len(generate_metrics) == 2, "Expected 2 generate_metrics" + # Remove potential sinks from internal usage of the lib (like http.client, used to communicate with + # the agent) + filtered_metrics = [metric.name for metric in generate_metrics.values() if metric.name != "executed.sink"] + assert filtered_metrics == ["executed.source", "request.tainted"] + assert len(filtered_metrics) == 2, "Expected 2 generate_metrics" assert span.get_metric(IAST_SPAN_TAGS.TELEMETRY_REQUEST_TAINTED) > 0 From 9490176bd32cfc3f1766f1d7902e3171f0b5ab80 Mon Sep 17 00:00:00 2001 From: Juanjo Alvarez Martinez Date: Fri, 10 May 2024 15:05:43 +0200 Subject: [PATCH 026/104] chore(asm): add SSRF support for http.client (#9207) ## Description Add SSRF taint sink support for the stdlib `http.client` module. ## Checklist - [X] Change(s) are motivated and described in the PR description - [X] Testing strategy is described if automated tests are not included in the PR - [X] Risks are described (performance impact, potential for breakage, maintainability) - [X] Change is maintainable (easy to change, telemetry, documentation) - [X] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [X] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [X] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [X] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Signed-off-by: Juanjo Alvarez Co-authored-by: Alberto Vara --- ddtrace/contrib/httplib/patch.py | 16 +++++++-- tests/appsec/iast/taint_sinks/test_ssrf.py | 39 ++++++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/ddtrace/contrib/httplib/patch.py b/ddtrace/contrib/httplib/patch.py index 47aa60bb03f..6d7f695c1e7 100644 --- a/ddtrace/contrib/httplib/patch.py +++ b/ddtrace/contrib/httplib/patch.py @@ -1,9 +1,12 @@ +import functools import os import sys from ddtrace import config +from ddtrace.appsec._common_module_patches import wrapped_request_D8CB81E472AF98A2 as _wrap_request_asm from ddtrace.internal.constants import COMPONENT from ddtrace.internal.schema.span_attribute_schema import SpanDirection +from ddtrace.settings.asm import config as asm_config from ddtrace.vendor import wrapt from ...constants import ANALYTICS_SAMPLE_RATE_KEY @@ -73,11 +76,20 @@ def _wrap_getresponse(func, instance, args, kwargs): log.debug("error applying request tags", exc_info=True) +def _call_asm_wrap(func, instance, *args, **kwargs): + _wrap_request_asm(func, instance, args, kwargs) + + def _wrap_request(func, instance, args, kwargs): # Use any attached tracer if available, otherwise use the global tracer + if asm_config._iast_enabled or asm_config._asm_enabled: + func_to_call = functools.partial(_call_asm_wrap, func, instance) + else: + func_to_call = func + pin = Pin.get_from(instance) if should_skip_request(pin, instance): - return func(*args, **kwargs) + return func_to_call(*args, **kwargs) cfg = config.get_from(instance) @@ -106,7 +118,7 @@ def _wrap_request(func, instance, args, kwargs): span.finish() try: - return func(*args, **kwargs) + return func_to_call(*args, **kwargs) except Exception: span = getattr(instance, "_datadog_span", None) exc_info = sys.exc_info() diff --git a/tests/appsec/iast/taint_sinks/test_ssrf.py b/tests/appsec/iast/taint_sinks/test_ssrf.py index 00a461ff77e..af651914bc6 100644 --- a/tests/appsec/iast/taint_sinks/test_ssrf.py +++ b/tests/appsec/iast/taint_sinks/test_ssrf.py @@ -6,6 +6,7 @@ from ddtrace.appsec._iast._taint_tracking import taint_pyobject from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect from ddtrace.appsec._iast.constants import VULN_SSRF +from ddtrace.contrib.httplib.patch import patch as httplib_patch from ddtrace.contrib.requests.patch import patch as requests_patch from ddtrace.contrib.urllib3.patch import patch as urllib3_patch from ddtrace.internal import core @@ -88,6 +89,25 @@ def test_ssrf_urllib3(tracer, iast_span_defaults): _check_report(span_report, tainted_path, "test_ssrf_urllib3") +def test_ssrf_httplib(tracer, iast_span_defaults): + with override_global_config(dict(_iast_enabled=True)): + httplib_patch() + import http.client + + tainted_url, tainted_path = _get_tainted_url() + try: + conn = http.client.HTTPConnection("localhost") + # label test_ssrf_httplib + conn.request("GET", tainted_url) + conn.getresponse() + except ConnectionError: + pass + + span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + assert span_report + _check_report(span_report, tainted_path, "test_ssrf_httplib") + + def _check_no_report_if_deduplicated(span_report, num_vuln_expected): if num_vuln_expected == 0: assert span_report is None @@ -130,3 +150,22 @@ def test_ssrf_urllib3_deduplication(num_vuln_expected, tracer, iast_span_dedupli span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_deduplication_enabled) _check_no_report_if_deduplicated(span_report, num_vuln_expected) + + +@pytest.mark.parametrize("num_vuln_expected", [1, 0, 0]) +def test_ssrf_httplib_deduplication(num_vuln_expected, tracer, iast_span_deduplication_enabled): + httplib_patch() + import http.client + + tainted_url, tainted_path = _get_tainted_url() + for _ in range(0, 5): + try: + conn = http.client.HTTPConnection("localhost") + # label test_ssrf_httplib_deduplication + conn.request("GET", tainted_url) + conn.getresponse() + except ConnectionError: + pass + + span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_deduplication_enabled) + _check_no_report_if_deduplicated(span_report, num_vuln_expected) From 5d96a5dda3f338ed71d128322f6e65af6d528350 Mon Sep 17 00:00:00 2001 From: Federico Mon Date: Fri, 10 May 2024 16:59:19 +0200 Subject: [PATCH 027/104] chore: remove release note (#9225) ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../notes/asm-gprc-import-not-crash-e13c48dcbcc1985e.yaml | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 releasenotes/notes/asm-gprc-import-not-crash-e13c48dcbcc1985e.yaml diff --git a/releasenotes/notes/asm-gprc-import-not-crash-e13c48dcbcc1985e.yaml b/releasenotes/notes/asm-gprc-import-not-crash-e13c48dcbcc1985e.yaml deleted file mode 100644 index aa02945302a..00000000000 --- a/releasenotes/notes/asm-gprc-import-not-crash-e13c48dcbcc1985e.yaml +++ /dev/null @@ -1,4 +0,0 @@ ---- -fixes: - - | - Code Security: Avoid an ``ImportError`` at patching stage if ``google._upb`` module is not available. From 7f679b39b34214b0b56b11142e5de88c307b8cc7 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Fri, 10 May 2024 17:20:09 +0200 Subject: [PATCH 028/104] chore(ci): disable flaky benchmark test (#9223) This benchmark test returns timeout and it's blocking the CI ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. - [x] If change touches code that signs or publishes builds or packages, or handles credentials of any kind, I've requested a review from `@DataDog/security-design-and-guidance`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .gitlab/benchmarks.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.gitlab/benchmarks.yml b/.gitlab/benchmarks.yml index 5e2f613b17b..17854287dda 100644 --- a/.gitlab/benchmarks.yml +++ b/.gitlab/benchmarks.yml @@ -103,10 +103,11 @@ appsec-iast-propagation: variables: SCENARIO: "appsec_iast_propagation" -benchmark-encoder: - extends: .benchmarks - variables: - SCENARIO: "encoder" +# Flaky benchmark test. Timeout errors +#benchmark-encoder: +# extends: .benchmarks +# variables: +# SCENARIO: "encoder" benchmark-http-propagation-extract: extends: .benchmarks From 78471452c9762f4443eb9db73584c25146d14c71 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Mon, 13 May 2024 09:30:09 +0200 Subject: [PATCH 029/104] chore(iast): remove flaky telemetry tests (#9213) ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. - [x] If change touches code that signs or publishes builds or packages, or handles credentials of any kind, I've requested a review from `@DataDog/security-design-and-guidance`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- tests/appsec/integrations/test_flask_telemetry.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/appsec/integrations/test_flask_telemetry.py b/tests/appsec/integrations/test_flask_telemetry.py index 7011380daf3..1408f0e216e 100644 --- a/tests/appsec/integrations/test_flask_telemetry.py +++ b/tests/appsec/integrations/test_flask_telemetry.py @@ -1,11 +1,10 @@ from ddtrace.appsec._handlers import _on_flask_patch from tests.appsec.appsec_utils import flask_server -from tests.utils import flaky from tests.utils import override_global_config -@flaky(until=1706677200) def test_iast_span_metrics(): + # TODO: move tests/telemetry/conftest.py::test_agent_session into a common conftest with flask_server(iast_enabled="true", token=None) as context: _, flask_client, pid = context @@ -13,7 +12,6 @@ def test_iast_span_metrics(): assert response.status_code == 200 assert response.content == b"OK" - # TODO: move tests/telemetry/conftest.py::test_agent_session into a common conftest def test_flask_instrumented_metrics(telemetry_writer): From d0f56abd6053409b0f00fd7d8a77c199fa5ccd36 Mon Sep 17 00:00:00 2001 From: Juanjo Alvarez Martinez Date: Mon, 13 May 2024 10:05:37 +0200 Subject: [PATCH 030/104] fix(asm): ensure new aspects does not raise exceptions if there is no context (#9234) ## Description Ensure the new aspect don't raise exceptions if there is no tx_map, instead just return the original function result. ## Checklist - [X] Change(s) are motivated and described in the PR description - [X] Testing strategy is described if automated tests are not included in the PR - [X] Risks are described (performance impact, potential for breakage, maintainability) - [X] Change is maintainable (easy to change, telemetry, documentation) - [X] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [X] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [X] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [X] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Signed-off-by: Juanjo Alvarez Co-authored-by: Alberto Vara --- .../_taint_tracking/Aspects/AspectSplit.cpp | 19 +++++----- .../_taint_tracking/Aspects/AspectsOsPath.cpp | 36 ++++++++----------- .../_iast/_taint_tracking/Aspects/Helpers.cpp | 4 --- .../aspects/test_ospath_aspects_fixtures.py | 27 ++++++++++++++ .../appsec/iast/aspects/test_split_aspect.py | 32 +++++++++++++++++ 5 files changed, 83 insertions(+), 35 deletions(-) diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectSplit.cpp b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectSplit.cpp index 5269270438d..d4aecd740d8 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectSplit.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectSplit.cpp @@ -5,13 +5,14 @@ template py::list api_split_text(const StrType& text, const optional& separator, const optional maxsplit) { + auto split = text.attr("split"); + auto split_result = split(separator, maxsplit); + TaintRangeMapType* tx_map = initializer->get_tainting_map(); if (not tx_map) { - throw py::value_error(MSG_ERROR_TAINT_MAP); + return split_result; } - auto split = text.attr("split"); - auto split_result = split(separator, maxsplit); auto ranges = api_get_ranges(text); if (not ranges.empty()) { set_ranges_on_splitted(text, ranges, split_result, tx_map, false); @@ -24,13 +25,13 @@ template py::list api_rsplit_text(const StrType& text, const optional& separator, const optional maxsplit) { + auto rsplit = text.attr("rsplit"); + auto split_result = rsplit(separator, maxsplit); TaintRangeMapType* tx_map = initializer->get_tainting_map(); if (not tx_map) { - throw py::value_error(MSG_ERROR_TAINT_MAP); + return split_result; } - auto rsplit = text.attr("rsplit"); - auto split_result = rsplit(separator, maxsplit); auto ranges = api_get_ranges(text); if (not ranges.empty()) { set_ranges_on_splitted(text, ranges, split_result, tx_map, false); @@ -42,13 +43,13 @@ template py::list api_splitlines_text(const StrType& text, bool keepends) { + auto splitlines = text.attr("splitlines"); + auto split_result = splitlines(keepends); TaintRangeMapType* tx_map = initializer->get_tainting_map(); if (not tx_map) { - throw py::value_error(MSG_ERROR_TAINT_MAP); + return split_result; } - auto splitlines = text.attr("splitlines"); - auto split_result = splitlines(keepends); auto ranges = api_get_ranges(text); if (not ranges.empty()) { set_ranges_on_splitted(text, ranges, split_result, tx_map, keepends); diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectsOsPath.cpp b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectsOsPath.cpp index 86055cf035f..213bf53b971 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectsOsPath.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectsOsPath.cpp @@ -103,15 +103,12 @@ template StrType api_ospathbasename_aspect(const StrType& path) { - auto tx_map = initializer->get_tainting_map(); - if (not tx_map) { - throw py::value_error(MSG_ERROR_TAINT_MAP); - } - auto ospath = py::module_::import("os.path"); auto basename = ospath.attr("basename"); auto basename_result = basename(path); - if (py::len(basename_result) == 0) { + + auto tx_map = initializer->get_tainting_map(); + if (not tx_map or py::len(basename_result) == 0) { return basename_result; } @@ -139,15 +136,12 @@ template StrType api_ospathdirname_aspect(const StrType& path) { - auto tx_map = initializer->get_tainting_map(); - if (not tx_map) { - throw py::value_error(MSG_ERROR_TAINT_MAP); - } - auto ospath = py::module_::import("os.path"); auto dirname = ospath.attr("dirname"); auto dirname_result = dirname(path); - if (py::len(dirname_result) == 0) { + + auto tx_map = initializer->get_tainting_map(); + if (not tx_map or py::len(dirname_result) == 0) { return dirname_result; } @@ -175,14 +169,12 @@ template static py::tuple _forward_to_set_ranges_on_splitted(const char* function_name, const StrType& path, bool includeseparator = false) { - auto tx_map = initializer->get_tainting_map(); - if (not tx_map) { - throw py::value_error(MSG_ERROR_TAINT_MAP); - } auto ospath = py::module_::import("os.path"); auto function = ospath.attr(function_name); auto function_result = function(path); - if (py::len(function_result) == 0) { + + auto tx_map = initializer->get_tainting_map(); + if (not tx_map or py::len(function_result) == 0) { return function_result; } @@ -229,15 +221,15 @@ template StrType api_ospathnormcase_aspect(const StrType& path) { - auto tx_map = initializer->get_tainting_map(); - if (not tx_map) { - throw py::value_error(MSG_ERROR_TAINT_MAP); - } - auto ospath = py::module_::import("os.path"); auto normcase = ospath.attr("normcase"); auto normcased = normcase(path); + auto tx_map = initializer->get_tainting_map(); + if (not tx_map) { + return normcased; + } + bool ranges_error; TaintRangeRefs ranges; std::tie(ranges, ranges_error) = get_ranges(path.ptr(), tx_map); diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/Helpers.cpp b/ddtrace/appsec/_iast/_taint_tracking/Aspects/Helpers.cpp index 5384d147303..b76a7d71e7e 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/Helpers.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/Helpers.cpp @@ -402,10 +402,6 @@ api_set_ranges_on_splitted(const StrType& source_str, bool include_separator) { TaintRangeMapType* tx_map = initializer->get_tainting_map(); - if (not tx_map) { - throw py::value_error(MSG_ERROR_TAINT_MAP); - } - return set_ranges_on_splitted(source_str, source_ranges, split_result, tx_map, include_separator); } diff --git a/tests/appsec/iast/aspects/test_ospath_aspects_fixtures.py b/tests/appsec/iast/aspects/test_ospath_aspects_fixtures.py index 8a10c996b2c..136f3d0fcc4 100644 --- a/tests/appsec/iast/aspects/test_ospath_aspects_fixtures.py +++ b/tests/appsec/iast/aspects/test_ospath_aspects_fixtures.py @@ -1,14 +1,19 @@ +import logging import os import sys import pytest +from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import Source from ddtrace.appsec._iast._taint_tracking import TaintRange +from ddtrace.appsec._iast._taint_tracking import create_context +from ddtrace.appsec._iast._taint_tracking import destroy_context from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges from ddtrace.appsec._iast._taint_tracking import taint_pyobject from tests.appsec.iast.aspects.conftest import _iast_patched_module +from tests.utils import override_env mod = _iast_patched_module("tests.appsec.iast.fixtures.aspects.module_functions") @@ -110,4 +115,26 @@ def test_ospathsplitdrive_tainted(): ] +@pytest.mark.skip_iast_check_logs +@pytest.mark.skipif(sys.version_info < (3, 9, 0), reason="Python version not supported by IAST") +def test_propagate_ranges_with_no_context(caplog): + """Test taint_pyobject without context. This test is to ensure that the function does not raise an exception.""" + input_str = "abcde" + create_context() + string_input = taint_pyobject( + pyobject=input_str, + source_name="test_add_aspect_tainting_left_hand", + source_value="foo", + source_origin=OriginType.PARAMETER, + ) + assert get_tainted_ranges(string_input) + + destroy_context() + with override_env({IAST.ENV_DEBUG: "true"}), caplog.at_level(logging.DEBUG): + result = mod.do_os_path_join(string_input, "bar") + assert result == "abcde/bar" + log_messages = [record.message for record in caplog.get_records("call")] + assert not any("[IAST] " in message for message in log_messages), log_messages + + # TODO: add tests for os.path.splitdrive and os.path.normcase under Windows diff --git a/tests/appsec/iast/aspects/test_split_aspect.py b/tests/appsec/iast/aspects/test_split_aspect.py index f7c9ec197e0..6ec65b90be5 100644 --- a/tests/appsec/iast/aspects/test_split_aspect.py +++ b/tests/appsec/iast/aspects/test_split_aspect.py @@ -1,12 +1,22 @@ +import logging +import sys + +import pytest + +from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast._taint_tracking import TaintRange from ddtrace.appsec._iast._taint_tracking import _aspect_rsplit from ddtrace.appsec._iast._taint_tracking import _aspect_split from ddtrace.appsec._iast._taint_tracking import _aspect_splitlines +from ddtrace.appsec._iast._taint_tracking import create_context +from ddtrace.appsec._iast._taint_tracking import destroy_context +from ddtrace.appsec._iast._taint_tracking import taint_pyobject from ddtrace.appsec._iast._taint_tracking._native.taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking._native.taint_tracking import Source from ddtrace.appsec._iast._taint_tracking._native.taint_tracking import get_ranges from ddtrace.appsec._iast._taint_tracking._native.taint_tracking import set_ranges from tests.appsec.iast.aspects.test_aspect_helpers import _build_sample_range +from tests.utils import override_env # These tests are simple ones testing the calls and replacements since most of the @@ -143,3 +153,25 @@ def test_aspect_splitlines_keepend_true(): assert get_ranges(res[0]) == [range1] assert get_ranges(res[1]) == [TaintRange(0, 4, Source("def\n", "sample_value", OriginType.PARAMETER))] assert get_ranges(res[2]) == [TaintRange(0, 4, Source("hij\n", "sample_value", OriginType.PARAMETER))] + + +@pytest.mark.skip_iast_check_logs +@pytest.mark.skipif(sys.version_info < (3, 9, 0), reason="Python version not supported by IAST") +def test_propagate_ranges_with_no_context(caplog): + """Test taint_pyobject without context. This test is to ensure that the function does not raise an exception.""" + input_str = "abc|def" + create_context() + string_input = taint_pyobject( + pyobject=input_str, + source_name="test_add_aspect_tainting_left_hand", + source_value="abc|def", + source_origin=OriginType.PARAMETER, + ) + assert get_ranges(string_input) + + destroy_context() + with override_env({IAST.ENV_DEBUG: "true"}), caplog.at_level(logging.DEBUG): + result = _aspect_split(string_input, "|") + assert result == ["abc", "def"] + log_messages = [record.getMessage() for record in caplog.get_records("call")] + assert not any("[IAST] " in message for message in log_messages), log_messages From 60be2421f91d761babcdc575a72d179a3f054138 Mon Sep 17 00:00:00 2001 From: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> Date: Mon, 13 May 2024 11:38:06 +0200 Subject: [PATCH 031/104] chore(asm): update libddwaf to 1.18.0 (#9237) - update libddwaf to 1.18.0 - remove the specific patch to avoid a bug introduced in 1.17.0 and now fixed. ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Co-authored-by: Alberto Vara --- ddtrace/appsec/_ddwaf/ddwaf_types.py | 4 ---- setup.py | 2 +- ...est_processor.test_appsec_body_no_collection_snapshot.json | 2 +- ..._processor.test_appsec_cookies_no_collection_snapshot.json | 2 +- ....appsec.test_processor.test_appsec_span_tags_snapshot.json | 2 +- ..._processor.test_appsec_span_tags_snapshot_with_errors.json | 2 +- ...ango.test_django_appsec_snapshots.test_appsec_enabled.json | 2 +- ...st_django_appsec_snapshots.test_appsec_enabled_attack.json | 2 +- ...jango_appsec_snapshots.test_request_ipblock_match_403.json | 2 +- ..._appsec_snapshots.test_request_ipblock_match_403_json.json | 2 +- ...ngo_appsec_snapshots.test_request_ipblock_nomatch_200.json | 2 +- ..._flask_ipblock_match_403[flask_appsec_good_rules_env].json | 2 +- ...sk_ipblock_match_403[flask_appsec_good_rules_env]_220.json | 2 +- ...k_ipblock_match_403_json[flask_appsec_good_rules_env].json | 2 +- ...block_match_403_json[flask_appsec_good_rules_env]_220.json | 2 +- ...lask_processexec_osspawn[flask_appsec_good_rules_env].json | 2 +- ..._processexec_osspawn[flask_appsec_good_rules_env]_220.json | 2 +- ...ask_processexec_ossystem[flask_appsec_good_rules_env].json | 2 +- ...processexec_ossystem[flask_appsec_good_rules_env]_220.json | 2 +- ...rocesscommunicatenoshell[flask_appsec_good_rules_env].json | 2 +- ...sscommunicatenoshell[flask_appsec_good_rules_env]_220.json | 2 +- ...bprocesscommunicateshell[flask_appsec_good_rules_env].json | 2 +- ...cesscommunicateshell[flask_appsec_good_rules_env]_220.json | 2 +- ...userblock_match_200_json[flask_appsec_good_rules_env].json | 2 +- ...block_match_200_json[flask_appsec_good_rules_env]_220.json | 2 +- ...userblock_match_403_json[flask_appsec_good_rules_env].json | 2 +- ...block_match_403_json[flask_appsec_good_rules_env]_220.json | 2 +- 27 files changed, 26 insertions(+), 30 deletions(-) diff --git a/ddtrace/appsec/_ddwaf/ddwaf_types.py b/ddtrace/appsec/_ddwaf/ddwaf_types.py index 302d4b6ad6d..ad5ce493121 100644 --- a/ddtrace/appsec/_ddwaf/ddwaf_types.py +++ b/ddtrace/appsec/_ddwaf/ddwaf_types.py @@ -160,10 +160,6 @@ def truncate_string(string: bytes) -> bytes: observator.truncation |= _TRUNC_CONTAINER_SIZE break res_key = truncate_string(key.encode("UTF-8", errors="ignore") if isinstance(key, str) else key) - # patch for libddwaf 1.17.0 - if key == "status_code" and isinstance(val, int): - val = str(val) - # end_patch obj = ddwaf_object( val, observator=observator, diff --git a/setup.py b/setup.py index 4d22108a01d..892325babb3 100644 --- a/setup.py +++ b/setup.py @@ -52,7 +52,7 @@ CURRENT_OS = platform.system() -LIBDDWAF_VERSION = "1.17.0" +LIBDDWAF_VERSION = "1.18.0" # Set macOS SDK default deployment target to 10.14 for C++17 support (if unset, may default to 10.9) if CURRENT_OS == "Darwin": diff --git a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_body_no_collection_snapshot.json b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_body_no_collection_snapshot.json index 4e0b841abc9..3aae9d04671 100644 --- a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_body_no_collection_snapshot.json +++ b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_body_no_collection_snapshot.json @@ -10,7 +10,7 @@ "meta": { "_dd.appsec.event_rules.version": "1.10.0", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"nfd-000-006\",\n \"name\": \"Detect failed attempt to fetch sensitive files\",\n \"tags\": {\n \"capec\": \"1000/118/169\",\n \"category\": \"attack_attempt\",\n \"confidence\": \"1\",\n \"cwe\": \"200\",\n \"type\": \"security_scanner\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"^404$\",\n \"parameters\": [\n {\n \"address\": \"server.response.status\",\n \"highlight\": [\n \"404\"\n ],\n \"key_path\": [],\n \"value\": \"404\"\n }\n ]\n },\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"\\\\.(cgi|bat|dll|exe|key|cert|crt|pem|der|pkcs|pkcs|pkcs[0-9]*|nsf|jsa|war|java|class|vb|vba|so|git|svn|hg|cvs)([^a-zA-Z0-9_]|$)\",\n \"parameters\": [\n {\n \"address\": \"server.request.uri.raw\",\n \"highlight\": [\n \".git\"\n ],\n \"key_path\": [],\n \"value\": \"/.git\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.17.0", + "_dd.appsec.waf.version": "1.18.0", "_dd.origin": "appsec", "_dd.p.dm": "-5", "_dd.runtime_family": "python", diff --git a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_cookies_no_collection_snapshot.json b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_cookies_no_collection_snapshot.json index d5551c31a02..bbde7eb3bb7 100644 --- a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_cookies_no_collection_snapshot.json +++ b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_cookies_no_collection_snapshot.json @@ -10,7 +10,7 @@ "meta": { "_dd.appsec.event_rules.version": "1.10.0", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"nfd-000-006\",\n \"name\": \"Detect failed attempt to fetch sensitive files\",\n \"tags\": {\n \"capec\": \"1000/118/169\",\n \"category\": \"attack_attempt\",\n \"confidence\": \"1\",\n \"cwe\": \"200\",\n \"type\": \"security_scanner\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"^404$\",\n \"parameters\": [\n {\n \"address\": \"server.response.status\",\n \"highlight\": [\n \"404\"\n ],\n \"key_path\": [],\n \"value\": \"404\"\n }\n ]\n },\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"\\\\.(cgi|bat|dll|exe|key|cert|crt|pem|der|pkcs|pkcs|pkcs[0-9]*|nsf|jsa|war|java|class|vb|vba|so|git|svn|hg|cvs)([^a-zA-Z0-9_]|$)\",\n \"parameters\": [\n {\n \"address\": \"server.request.uri.raw\",\n \"highlight\": [\n \".git\"\n ],\n \"key_path\": [],\n \"value\": \"/.git\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.17.0", + "_dd.appsec.waf.version": "1.18.0", "_dd.origin": "appsec", "_dd.p.dm": "-5", "_dd.runtime_family": "python", diff --git a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot.json b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot.json index c64d9557a48..c5fa864ba82 100644 --- a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot.json +++ b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot.json @@ -10,7 +10,7 @@ "meta": { "_dd.appsec.event_rules.version": "1.10.0", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"nfd-000-006\",\n \"name\": \"Detect failed attempt to fetch sensitive files\",\n \"tags\": {\n \"capec\": \"1000/118/169\",\n \"category\": \"attack_attempt\",\n \"confidence\": \"1\",\n \"cwe\": \"200\",\n \"type\": \"security_scanner\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"^404$\",\n \"parameters\": [\n {\n \"address\": \"server.response.status\",\n \"highlight\": [\n \"404\"\n ],\n \"key_path\": [],\n \"value\": \"404\"\n }\n ]\n },\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"\\\\.(cgi|bat|dll|exe|key|cert|crt|pem|der|pkcs|pkcs|pkcs[0-9]*|nsf|jsa|war|java|class|vb|vba|so|git|svn|hg|cvs)([^a-zA-Z0-9_]|$)\",\n \"parameters\": [\n {\n \"address\": \"server.request.uri.raw\",\n \"highlight\": [\n \".git\"\n ],\n \"key_path\": [],\n \"value\": \"/.git\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.17.0", + "_dd.appsec.waf.version": "1.18.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.dm": "-5", diff --git a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot_with_errors.json b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot_with_errors.json index 8ac32a9a972..86fcfba9f3f 100644 --- a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot_with_errors.json +++ b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot_with_errors.json @@ -10,7 +10,7 @@ "meta": { "_dd.appsec.event_rules.errors": "{\"missing key 'conditions'\": [\"crs-913-110\"], \"missing key 'tags'\": [\"crs-942-100\"]}", "_dd.appsec.event_rules.version": "5.5.5", - "_dd.appsec.waf.version": "1.17.0", + "_dd.appsec.waf.version": "1.18.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.runtime_family": "python", diff --git a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled.json b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled.json index 645d88357bc..95e470bdd53 100644 --- a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled.json +++ b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled.json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "1.10.0", - "_dd.appsec.waf.version": "1.17.0", + "_dd.appsec.waf.version": "1.18.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled_attack.json b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled_attack.json index da3ee1862b0..e2c6177c524 100644 --- a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled_attack.json +++ b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled_attack.json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "1.10.0", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"nfd-000-006\",\n \"name\": \"Detect failed attempt to fetch sensitive files\",\n \"tags\": {\n \"capec\": \"1000/118/169\",\n \"category\": \"attack_attempt\",\n \"confidence\": \"1\",\n \"cwe\": \"200\",\n \"type\": \"security_scanner\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"^404$\",\n \"parameters\": [\n {\n \"address\": \"server.response.status\",\n \"highlight\": [\n \"404\"\n ],\n \"key_path\": [],\n \"value\": \"404\"\n }\n ]\n },\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"\\\\.(cgi|bat|dll|exe|key|cert|crt|pem|der|pkcs|pkcs|pkcs[0-9]*|nsf|jsa|war|java|class|vb|vba|so|git|svn|hg|cvs)([^a-zA-Z0-9_]|$)\",\n \"parameters\": [\n {\n \"address\": \"server.request.uri.raw\",\n \"highlight\": [\n \".git\"\n ],\n \"key_path\": [],\n \"value\": \"/.git\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.17.0", + "_dd.appsec.waf.version": "1.18.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.dm": "-5", diff --git a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403.json b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403.json index 3f1f6e7faaf..80089fca74f 100644 --- a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403.json +++ b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403.json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[{\"rule\":{\"id\":\"blk-001-001\",\"name\":\"Block IP addresses\",\"on_match\":[\"block\"],\"tags\":{\"category\":\"blocking\",\"type\":\"ip_addresses\"}},\"rule_matches\":[{\"operator\":\"ip_match\",\"operator_value\":\"\",\"parameters\":[{\"address\":\"http.client_ip\",\"key_path\":[],\"value\":\"8.8.4.4\",\"highlight\":[\"8.8.4.4\"]}]}],\"span_id\":10192376353237234254}]}", - "_dd.appsec.waf.version": "1.17.0", + "_dd.appsec.waf.version": "1.18.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.dm": "-5", diff --git a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403_json.json b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403_json.json index 22c057b44bb..f6365ebbfbc 100644 --- a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403_json.json +++ b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403_json.json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[{\"rule\":{\"id\":\"blk-001-001\",\"name\":\"Block IP addresses\",\"on_match\":[\"block\"],\"tags\":{\"category\":\"blocking\",\"type\":\"ip_addresses\"}},\"rule_matches\":[{\"operator\":\"ip_match\",\"operator_value\":\"\",\"parameters\":[{\"address\":\"http.client_ip\",\"key_path\":[],\"value\":\"8.8.4.4\",\"highlight\":[\"8.8.4.4\"]}]}],\"span_id\":865087550764298227}]}", - "_dd.appsec.waf.version": "1.17.0", + "_dd.appsec.waf.version": "1.18.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.dm": "-5", diff --git a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_nomatch_200.json b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_nomatch_200.json index cb309788bd3..20d83c5a81a 100644 --- a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_nomatch_200.json +++ b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_nomatch_200.json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.17.0", + "_dd.appsec.waf.version": "1.18.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env].json index 041200eed48..227c3d6ff0e 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env].json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"blk-001-001\",\n \"name\": \"Block IP addresses\",\n \"on_match\": [\n \"block\"\n ],\n \"tags\": {\n \"category\": \"blocking\",\n \"type\": \"ip_addresses\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"ip_match\",\n \"operator_value\": \"\",\n \"parameters\": [\n {\n \"address\": \"http.client_ip\",\n \"highlight\": [\n \"8.8.4.4\"\n ],\n \"key_path\": [],\n \"value\": \"8.8.4.4\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.17.0", + "_dd.appsec.waf.version": "1.18.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.dm": "-5", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env]_220.json index 1b52247ba45..2434e6c71ef 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env]_220.json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"blk-001-001\",\n \"name\": \"Block IP addresses\",\n \"on_match\": [\n \"block\"\n ],\n \"tags\": {\n \"category\": \"blocking\",\n \"type\": \"ip_addresses\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"ip_match\",\n \"operator_value\": \"\",\n \"parameters\": [\n {\n \"address\": \"http.client_ip\",\n \"highlight\": [\n \"8.8.4.4\"\n ],\n \"key_path\": [],\n \"value\": \"8.8.4.4\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.17.0", + "_dd.appsec.waf.version": "1.18.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.dm": "-5", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env].json index 067190bae89..496f5c40998 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env].json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"blk-001-001\",\n \"name\": \"Block IP addresses\",\n \"on_match\": [\n \"block\"\n ],\n \"tags\": {\n \"category\": \"blocking\",\n \"type\": \"ip_addresses\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"ip_match\",\n \"operator_value\": \"\",\n \"parameters\": [\n {\n \"address\": \"http.client_ip\",\n \"highlight\": [\n \"8.8.4.4\"\n ],\n \"key_path\": [],\n \"value\": \"8.8.4.4\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.17.0", + "_dd.appsec.waf.version": "1.18.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.dm": "-5", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env]_220.json index 1751704a1c9..e5af43c6901 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env]_220.json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"blk-001-001\",\n \"name\": \"Block IP addresses\",\n \"on_match\": [\n \"block\"\n ],\n \"tags\": {\n \"category\": \"blocking\",\n \"type\": \"ip_addresses\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"ip_match\",\n \"operator_value\": \"\",\n \"parameters\": [\n {\n \"address\": \"http.client_ip\",\n \"highlight\": [\n \"8.8.4.4\"\n ],\n \"key_path\": [],\n \"value\": \"8.8.4.4\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.17.0", + "_dd.appsec.waf.version": "1.18.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.dm": "-5", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env].json index 471c3b94944..e470ba509a5 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env].json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.17.0", + "_dd.appsec.waf.version": "1.18.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env]_220.json index 2e4879e4e5b..d27f02d94e5 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env]_220.json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.17.0", + "_dd.appsec.waf.version": "1.18.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env].json index 5e551bab6c4..83566fae407 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env].json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.17.0", + "_dd.appsec.waf.version": "1.18.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env]_220.json index c767121db76..d2f5264e869 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env]_220.json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.17.0", + "_dd.appsec.waf.version": "1.18.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env].json index 8d254075a96..06c532e5af5 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env].json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.17.0", + "_dd.appsec.waf.version": "1.18.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env]_220.json index cf2beee8288..827c5946399 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env]_220.json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.17.0", + "_dd.appsec.waf.version": "1.18.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env].json index d0c8a7ddd6d..9746bfdde50 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env].json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.17.0", + "_dd.appsec.waf.version": "1.18.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env]_220.json index 84584becf10..a9c5594a43c 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env]_220.json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.17.0", + "_dd.appsec.waf.version": "1.18.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env].json index 9d280cdf4a5..29a0c9dae32 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env].json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.17.0", + "_dd.appsec.waf.version": "1.18.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env]_220.json index 413f28235d3..506121e72ef 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env]_220.json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.17.0", + "_dd.appsec.waf.version": "1.18.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env].json index 38799b44471..23fc907a22c 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env].json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"blk-001-002\",\n \"name\": \"Block User Addresses\",\n \"on_match\": [\n \"block\"\n ],\n \"tags\": {\n \"category\": \"security_response\",\n \"type\": \"block_user\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"exact_match\",\n \"operator_value\": \"\",\n \"parameters\": [\n {\n \"address\": \"usr.id\",\n \"highlight\": [\n \"123456\"\n ],\n \"key_path\": [],\n \"value\": \"123456\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.17.0", + "_dd.appsec.waf.version": "1.18.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.dm": "-5", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env]_220.json index 2978faf822b..bd9fbfec82e 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env]_220.json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"blk-001-002\",\n \"name\": \"Block User Addresses\",\n \"on_match\": [\n \"block\"\n ],\n \"tags\": {\n \"category\": \"security_response\",\n \"type\": \"block_user\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"exact_match\",\n \"operator_value\": \"\",\n \"parameters\": [\n {\n \"address\": \"usr.id\",\n \"highlight\": [\n \"123456\"\n ],\n \"key_path\": [],\n \"value\": \"123456\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.17.0", + "_dd.appsec.waf.version": "1.18.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.dm": "-5", From fccb8b3d2076e8176c80d93e930c530530eb9257 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Mon, 13 May 2024 12:41:14 +0200 Subject: [PATCH 032/104] chore(iast): cmdi instrumented sink metric (#9214) Enable CMDi instrumented sink metric ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. - [x] If change touches code that signs or publishes builds or packages, or handles credentials of any kind, I've requested a review from `@DataDog/security-design-and-guidance`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> --- .../appsec/_iast/taint_sinks/command_injection.py | 3 +++ tests/appsec/iast/test_telemetry.py | 14 ++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/ddtrace/appsec/_iast/taint_sinks/command_injection.py b/ddtrace/appsec/_iast/taint_sinks/command_injection.py index c95d99567cd..ad98fd6286b 100644 --- a/ddtrace/appsec/_iast/taint_sinks/command_injection.py +++ b/ddtrace/appsec/_iast/taint_sinks/command_injection.py @@ -10,6 +10,7 @@ from ..._constants import IAST_SPAN_TAGS from .. import oce +from .._metrics import _set_metric_iast_instrumented_sink from .._metrics import increment_iast_span_metric from ..constants import VULN_CMDI from ..processor import AppSecIastSpanProcessor @@ -39,6 +40,8 @@ def patch(): os._datadog_cmdi_patch = True subprocess._datadog_cmdi_patch = True + _set_metric_iast_instrumented_sink(VULN_CMDI) + if asm_config._ep_enabled: core.dispatch("exploit.prevention.ssrf.patch.urllib") diff --git a/tests/appsec/iast/test_telemetry.py b/tests/appsec/iast/test_telemetry.py index c36ac3ad44c..460ba0467a8 100644 --- a/tests/appsec/iast/test_telemetry.py +++ b/tests/appsec/iast/test_telemetry.py @@ -12,6 +12,7 @@ from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import origin_to_str from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast.taint_sinks.command_injection import patch as cmdi_patch from ddtrace.ext import SpanTypes from ddtrace.internal.telemetry.constants import TELEMETRY_NAMESPACE_TAG_IAST from ddtrace.internal.telemetry.constants import TELEMETRY_TYPE_GENERATE_METRICS @@ -75,6 +76,19 @@ def test_metric_executed_sink(no_request_sampling, telemetry_writer): assert span.get_metric(IAST_SPAN_TAGS.TELEMETRY_REQUEST_TAINTED) is None +def test_metric_instrumented_cmdi(no_request_sampling, telemetry_writer): + with override_env(dict(DD_IAST_TELEMETRY_VERBOSITY="INFORMATION")), override_global_config( + dict(_iast_enabled=True) + ): + cmdi_patch() + + metrics_result = telemetry_writer._namespace._metrics_data + generate_metrics = metrics_result[TELEMETRY_TYPE_GENERATE_METRICS][TELEMETRY_NAMESPACE_TAG_IAST] + assert [metric.name for metric in generate_metrics.values()] == ["instrumented.sink"] + assert [metric._tags for metric in generate_metrics.values()] == [(("vulnerability_type", "COMMAND_INJECTION"),)] + assert len(generate_metrics) == 1, "Expected 1 generate_metrics" + + def test_metric_instrumented_propagation(no_request_sampling, telemetry_writer): with override_env(dict(DD_IAST_TELEMETRY_VERBOSITY="INFORMATION")), override_global_config( dict(_iast_enabled=True) From 78f361ac2384ac7bfe5b0621e74c3d2834bec43c Mon Sep 17 00:00:00 2001 From: Juanjo Alvarez Martinez Date: Mon, 13 May 2024 14:39:51 +0200 Subject: [PATCH 033/104] chore(asm): add SSRF support for webbrowser.open (#9209) ## Description Add SSRF taint sink support for the stdlib `webbrowser` module. ## Checklist - [X] Change(s) are motivated and described in the PR description - [X] Testing strategy is described if automated tests are not included in the PR - [X] Risks are described (performance impact, potential for breakage, maintainability) - [X] Change is maintainable (easy to change, telemetry, documentation) - [X] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [X] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [X] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [X] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Signed-off-by: Juanjo Alvarez Co-authored-by: Alberto Vara --- .github/CODEOWNERS | 1 + ddtrace/appsec/_iast/taint_sinks/ssrf.py | 25 ++- ddtrace/contrib/webbrowser/__init__.py | 12 ++ ddtrace/contrib/webbrowser/patch.py | 35 ++++ tests/.suitespec.json | 11 ++ tests/appsec/iast/taint_sinks/test_ssrf.py | 197 ++++++++++++++------- 6 files changed, 213 insertions(+), 68 deletions(-) create mode 100644 ddtrace/contrib/webbrowser/__init__.py create mode 100644 ddtrace/contrib/webbrowser/patch.py diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 485214bbe12..44659ae5dc0 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -63,6 +63,7 @@ ddtrace/appsec/ @DataDog/asm-python ddtrace/settings/asm.py @DataDog/asm-python ddtrace/contrib/subprocess/ @DataDog/asm-python ddtrace/contrib/flask_login/ @DataDog/asm-python +ddtrace/contrib/webbrowser @DataDog/asm-python ddtrace/internal/_exceptions.py @DataDog/asm-python tests/appsec/ @DataDog/asm-python tests/contrib/dbapi/test_dbapi_appsec.py @DataDog/asm-python diff --git a/ddtrace/appsec/_iast/taint_sinks/ssrf.py b/ddtrace/appsec/_iast/taint_sinks/ssrf.py index 40b0f2fdb5e..4e9a332b18a 100644 --- a/ddtrace/appsec/_iast/taint_sinks/ssrf.py +++ b/ddtrace/appsec/_iast/taint_sinks/ssrf.py @@ -1,6 +1,9 @@ from typing import Callable from ddtrace.internal.logger import get_logger +from ddtrace.internal.utils import ArgumentError +from ddtrace.internal.utils import get_argument_value +from ddtrace.internal.utils.importlib import func_name from ..._constants import IAST_SPAN_TAGS from .. import oce @@ -18,8 +21,28 @@ class SSRF(VulnerabilityBase): vulnerability_type = VULN_SSRF +_FUNC_TO_URL_ARGUMENT = { + "http.client.request": (1, "url"), + "requests.sessions.request": (1, "url"), + "urllib3._request_methods.request": (1, "url"), + "urllib3.request.request": (1, "url"), + "webbrowser.open": (0, "url"), +} + + def _iast_report_ssrf(func: Callable, *args, **kwargs): - report_ssrf = args[1] if len(args) > 1 else kwargs.get("url", None) + func_key = func_name(func) + arg_pos, kwarg_name = _FUNC_TO_URL_ARGUMENT.get(func_key, (None, None)) + if arg_pos is None: + log.debug("%s not found in list of functions supported for SSRF", func_key) + return + + try: + kw = kwarg_name if kwarg_name else "" + report_ssrf = get_argument_value(list(args), kwargs, arg_pos, kw) + except ArgumentError: + log.debug("Failed to get URL argument from _FUNC_TO_URL_ARGUMENT dict for function %s", func_key) + return if report_ssrf: from .._metrics import _set_metric_iast_executed_sink diff --git a/ddtrace/contrib/webbrowser/__init__.py b/ddtrace/contrib/webbrowser/__init__.py new file mode 100644 index 00000000000..ff810922060 --- /dev/null +++ b/ddtrace/contrib/webbrowser/__init__.py @@ -0,0 +1,12 @@ +""" +Trace the standard library ``webbrowser`` library to trace +HTTP requests and detect SSRF vulnerabilities. It is enabled by default +if ``DD_IAST_ENABLED`` is set to ``True`` (for detecting sink points) and/or +``DD_ASM_ENABLED`` is set to ``True`` (for exploit prevention). +""" +from .patch import get_version +from .patch import patch +from .patch import unpatch + + +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/webbrowser/patch.py b/ddtrace/contrib/webbrowser/patch.py new file mode 100644 index 00000000000..c974c56eda6 --- /dev/null +++ b/ddtrace/contrib/webbrowser/patch.py @@ -0,0 +1,35 @@ +import webbrowser + +from ddtrace.appsec._common_module_patches import wrapped_request_D8CB81E472AF98A2 as _wrap_open +from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_sink +from ddtrace.appsec._iast.constants import VULN_SSRF +from ddtrace.settings.asm import config as asm_config +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w + +from ..trace_utils import unwrap as _u + + +def get_version(): + # type: () -> str + return "" + + +def patch(): + """patch the built-in webbrowser methods for tracing""" + if getattr(webbrowser, "__datadog_patch", False): + return + webbrowser.__datadog_patch = True + + _w("webbrowser", "open", _wrap_open) + + if asm_config._iast_enabled: + _set_metric_iast_instrumented_sink(VULN_SSRF) + + +def unpatch(): + """unpatch any previously patched modules""" + if not getattr(webbrowser, "__datadog_patch", False): + return + webbrowser.__datadog_patch = False + + _u(webbrowser, "open") diff --git a/tests/.suitespec.json b/tests/.suitespec.json index e1d036b4581..e11b5e8b94e 100644 --- a/tests/.suitespec.json +++ b/tests/.suitespec.json @@ -262,6 +262,9 @@ "urllib3": [ "ddtrace/contrib/urllib3/*" ], + "webbrowser": [ + "ddtrace/contrib/webbrowser/*" + ], "rq": [ "ddtrace/contrib/rq/*" ], @@ -1287,6 +1290,14 @@ "tests/contrib/urllib3/*", "tests/snapshots/tests.contrib.urllib3.*" ], + "webbrowser": [ + "@bootstrap", + "@core", + "@contrib", + "@tracing", + "@webbrowser", + "tests/appsec/iast/taint_sinks/test_ssrf.py" + ], "vertica": [ "@bootstrap", "@core", diff --git a/tests/appsec/iast/taint_sinks/test_ssrf.py b/tests/appsec/iast/taint_sinks/test_ssrf.py index af651914bc6..a01c38dffa9 100644 --- a/tests/appsec/iast/taint_sinks/test_ssrf.py +++ b/tests/appsec/iast/taint_sinks/test_ssrf.py @@ -7,8 +7,13 @@ from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect from ddtrace.appsec._iast.constants import VULN_SSRF from ddtrace.contrib.httplib.patch import patch as httplib_patch +from ddtrace.contrib.httplib.patch import unpatch as httplib_unpatch from ddtrace.contrib.requests.patch import patch as requests_patch +from ddtrace.contrib.requests.patch import unpatch as requests_unpatch from ddtrace.contrib.urllib3.patch import patch as urllib3_patch +from ddtrace.contrib.urllib3.patch import unpatch as urllib3_unpatch +from ddtrace.contrib.webbrowser.patch import patch as webbrowser_patch +from ddtrace.contrib.webbrowser.patch import unpatch as webbrowser_unpatch from ddtrace.internal import core from tests.appsec.iast.iast_utils import get_line_and_hash from tests.utils import override_global_config @@ -57,55 +62,84 @@ def _check_report(span_report, tainted_path, label): def test_ssrf_requests(tracer, iast_span_defaults): with override_global_config(dict(_iast_enabled=True)): requests_patch() - import requests - from requests.exceptions import ConnectionError - - tainted_url, tainted_path = _get_tainted_url() try: - # label test_ssrf_requests - requests.get(tainted_url) - except ConnectionError: - pass + import requests + from requests.exceptions import ConnectionError - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) - assert span_report - _check_report(span_report, tainted_path, "test_ssrf_requests") + tainted_url, tainted_path = _get_tainted_url() + try: + # label test_ssrf_requests + requests.get(tainted_url) + except ConnectionError: + pass + + span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + assert span_report + _check_report(span_report, tainted_path, "test_ssrf_requests") + finally: + requests_unpatch() def test_ssrf_urllib3(tracer, iast_span_defaults): with override_global_config(dict(_iast_enabled=True)): urllib3_patch() - import urllib3 - - tainted_url, tainted_path = _get_tainted_url() try: - # label test_ssrf_urllib3 - urllib3.request(method="GET", url=tainted_url) - except urllib3.exceptions.HTTPError: - pass + import urllib3 - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) - assert span_report - _check_report(span_report, tainted_path, "test_ssrf_urllib3") + tainted_url, tainted_path = _get_tainted_url() + try: + # label test_ssrf_urllib3 + urllib3.request(method="GET", url=tainted_url) + except urllib3.exceptions.HTTPError: + pass + + span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + assert span_report + _check_report(span_report, tainted_path, "test_ssrf_urllib3") + finally: + urllib3_unpatch() def test_ssrf_httplib(tracer, iast_span_defaults): with override_global_config(dict(_iast_enabled=True)): httplib_patch() - import http.client + try: + import http.client - tainted_url, tainted_path = _get_tainted_url() + tainted_url, tainted_path = _get_tainted_url() + try: + conn = http.client.HTTPConnection("localhost") + # label test_ssrf_httplib + conn.request("GET", tainted_url) + conn.getresponse() + except ConnectionError: + pass + + span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + assert span_report + _check_report(span_report, tainted_path, "test_ssrf_httplib") + finally: + httplib_unpatch() + + +def test_ssrf_webbrowser(tracer, iast_span_defaults): + with override_global_config(dict(_iast_enabled=True)): + webbrowser_patch() try: - conn = http.client.HTTPConnection("localhost") - # label test_ssrf_httplib - conn.request("GET", tainted_url) - conn.getresponse() - except ConnectionError: - pass - - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) - assert span_report - _check_report(span_report, tainted_path, "test_ssrf_httplib") + import webbrowser + + tainted_url, tainted_path = _get_tainted_url() + try: + # label test_ssrf_webbrowser + webbrowser.open(tainted_url) + except ConnectionError: + pass + + span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + assert span_report + _check_report(span_report, tainted_path, "test_ssrf_webbrowser") + finally: + webbrowser_unpatch() def _check_no_report_if_deduplicated(span_report, num_vuln_expected): @@ -120,52 +154,81 @@ def _check_no_report_if_deduplicated(span_report, num_vuln_expected): @pytest.mark.parametrize("num_vuln_expected", [1, 0, 0]) def test_ssrf_requests_deduplication(num_vuln_expected, tracer, iast_span_deduplication_enabled): requests_patch() - import requests - from requests.exceptions import ConnectionError + try: + import requests + from requests.exceptions import ConnectionError - tainted_url, tainted_path = _get_tainted_url() - for _ in range(0, 5): - try: - # label test_ssrf_requests_deduplication - requests.get(tainted_url) - except ConnectionError: - pass + tainted_url, tainted_path = _get_tainted_url() + for _ in range(0, 5): + try: + # label test_ssrf_requests_deduplication + requests.get(tainted_url) + except ConnectionError: + pass - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_deduplication_enabled) - _check_no_report_if_deduplicated(span_report, num_vuln_expected) + span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_deduplication_enabled) + _check_no_report_if_deduplicated(span_report, num_vuln_expected) + finally: + requests_unpatch() @pytest.mark.parametrize("num_vuln_expected", [1, 0, 0]) def test_ssrf_urllib3_deduplication(num_vuln_expected, tracer, iast_span_deduplication_enabled): urllib3_patch() - import urllib3 + try: + import urllib3 - tainted_url, tainted_path = _get_tainted_url() - for _ in range(0, 5): - try: - # label test_ssrf_urllib3_deduplication - urllib3.request(method="GET", url=tainted_url) - except urllib3.exceptions.HTTPError: - pass + tainted_url, tainted_path = _get_tainted_url() + for _ in range(0, 5): + try: + # label test_ssrf_urllib3_deduplication + urllib3.request(method="GET", url=tainted_url) + except urllib3.exceptions.HTTPError: + pass - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_deduplication_enabled) - _check_no_report_if_deduplicated(span_report, num_vuln_expected) + span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_deduplication_enabled) + _check_no_report_if_deduplicated(span_report, num_vuln_expected) + finally: + requests_unpatch() @pytest.mark.parametrize("num_vuln_expected", [1, 0, 0]) def test_ssrf_httplib_deduplication(num_vuln_expected, tracer, iast_span_deduplication_enabled): httplib_patch() - import http.client + try: + import http.client - tainted_url, tainted_path = _get_tainted_url() - for _ in range(0, 5): - try: - conn = http.client.HTTPConnection("localhost") - # label test_ssrf_httplib_deduplication - conn.request("GET", tainted_url) - conn.getresponse() - except ConnectionError: - pass - - span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_deduplication_enabled) - _check_no_report_if_deduplicated(span_report, num_vuln_expected) + tainted_url, tainted_path = _get_tainted_url() + for _ in range(0, 5): + try: + conn = http.client.HTTPConnection("localhost") + # label test_ssrf_httplib_deduplication + conn.request("GET", tainted_url) + conn.getresponse() + except ConnectionError: + pass + + span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_deduplication_enabled) + _check_no_report_if_deduplicated(span_report, num_vuln_expected) + finally: + httplib_unpatch() + + +@pytest.mark.parametrize("num_vuln_expected", [1, 0, 0]) +def test_ssrf_webbrowser_deduplication(num_vuln_expected, tracer, iast_span_deduplication_enabled): + webbrowser_patch() + try: + import webbrowser + + tainted_url, tainted_path = _get_tainted_url() + for _ in range(0, 5): + try: + # label test_ssrf_webbrowser_deduplication + webbrowser.open(tainted_url) + except ConnectionError: + pass + + span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_deduplication_enabled) + _check_no_report_if_deduplicated(span_report, num_vuln_expected) + finally: + webbrowser_unpatch() From 228ac3b249d33f1b52bde31b2b8615c904bfacad Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Mon, 13 May 2024 16:47:17 +0200 Subject: [PATCH 034/104] chore(iast): sqli and ssrf instrumented sink metric (#9241) Enable SQLi and SSRF instrumented sink metric This PR continues #9214 APPSEC-52863 & APPSEC-52863 ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. - [x] If change touches code that signs or publishes builds or packages, or handles credentials of any kind, I've requested a review from `@DataDog/security-design-and-guidance`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Co-authored-by: Brett Langdon --- ddtrace/contrib/mysql/patch.py | 6 +++ ddtrace/contrib/mysqldb/patch.py | 6 +++ ddtrace/contrib/requests/patch.py | 6 +++ ddtrace/contrib/sqlalchemy/patch.py | 6 +++ ddtrace/contrib/sqlite3/patch.py | 6 +++ ddtrace/contrib/urllib3/patch.py | 6 +++ tests/appsec/iast/test_telemetry.py | 66 ++++++++++++++++++++++++++--- 7 files changed, 97 insertions(+), 5 deletions(-) diff --git a/ddtrace/contrib/mysql/patch.py b/ddtrace/contrib/mysql/patch.py index cfb1f17a7da..280f36329f9 100644 --- a/ddtrace/contrib/mysql/patch.py +++ b/ddtrace/contrib/mysql/patch.py @@ -4,7 +4,10 @@ from ddtrace import Pin from ddtrace import config +from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_sink +from ddtrace.appsec._iast.constants import VULN_SQL_INJECTION from ddtrace.contrib.dbapi import TracedConnection +from ddtrace.settings.asm import config as asm_config from ddtrace.vendor import wrapt from ...ext import db @@ -47,6 +50,9 @@ def patch(): if hasattr(mysql.connector, "Connect"): mysql.connector.Connect = mysql.connector.connect + if asm_config._iast_enabled: + _set_metric_iast_instrumented_sink(VULN_SQL_INJECTION) + def unpatch(): if isinstance(mysql.connector.connect, wrapt.ObjectProxy): diff --git a/ddtrace/contrib/mysqldb/patch.py b/ddtrace/contrib/mysqldb/patch.py index a7a0d2b1ff8..315cfbc3bd3 100644 --- a/ddtrace/contrib/mysqldb/patch.py +++ b/ddtrace/contrib/mysqldb/patch.py @@ -4,12 +4,15 @@ from ddtrace import Pin from ddtrace import config +from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_sink +from ddtrace.appsec._iast.constants import VULN_SQL_INJECTION from ddtrace.constants import SPAN_KIND from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.contrib.dbapi import TracedConnection from ddtrace.contrib.trace_utils import ext_service from ddtrace.internal.constants import COMPONENT from ddtrace.internal.schema import schematize_database_operation +from ddtrace.settings.asm import config as asm_config from ddtrace.vendor.wrapt import wrap_function_wrapper as _w from ...ext import SpanKind @@ -63,6 +66,9 @@ def patch(): if hasattr(MySQLdb, "connect"): _w("MySQLdb", "connect", _connect) + if asm_config._iast_enabled: + _set_metric_iast_instrumented_sink(VULN_SQL_INJECTION) + def unpatch(): if not getattr(MySQLdb, "__datadog_patch", False): diff --git a/ddtrace/contrib/requests/patch.py b/ddtrace/contrib/requests/patch.py index d593029e76c..27cfb36fc9f 100644 --- a/ddtrace/contrib/requests/patch.py +++ b/ddtrace/contrib/requests/patch.py @@ -4,6 +4,9 @@ from ddtrace import config from ddtrace.appsec._common_module_patches import wrapped_request_D8CB81E472AF98A2 as _wrap_request +from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_sink +from ddtrace.appsec._iast.constants import VULN_SSRF +from ddtrace.settings.asm import config as asm_config from ddtrace.vendor.wrapt import wrap_function_wrapper as _w from ...internal.schema import schematize_service_name @@ -41,6 +44,9 @@ def patch(): _w("requests", "Session.request", _wrap_request) Pin(_config=config.requests).onto(requests.Session) + if asm_config._iast_enabled: + _set_metric_iast_instrumented_sink(VULN_SSRF) + def unpatch(): """Disable traced sessions""" diff --git a/ddtrace/contrib/sqlalchemy/patch.py b/ddtrace/contrib/sqlalchemy/patch.py index edd1c43192c..01bd789e663 100644 --- a/ddtrace/contrib/sqlalchemy/patch.py +++ b/ddtrace/contrib/sqlalchemy/patch.py @@ -1,5 +1,8 @@ import sqlalchemy +from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_sink +from ddtrace.appsec._iast.constants import VULN_SQL_INJECTION +from ddtrace.settings.asm import config as asm_config from ddtrace.vendor.wrapt import wrap_function_wrapper as _w from ..trace_utils import unwrap @@ -20,6 +23,9 @@ def patch(): _w("sqlalchemy", "create_engine", _wrap_create_engine) _w("sqlalchemy.engine", "create_engine", _wrap_create_engine) + if asm_config._iast_enabled: + _set_metric_iast_instrumented_sink(VULN_SQL_INJECTION) + def unpatch(): # unpatch sqlalchemy diff --git a/ddtrace/contrib/sqlite3/patch.py b/ddtrace/contrib/sqlite3/patch.py index b5e357b3a22..52d3d5c329a 100644 --- a/ddtrace/contrib/sqlite3/patch.py +++ b/ddtrace/contrib/sqlite3/patch.py @@ -4,6 +4,9 @@ import sys from ddtrace import config +from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_sink +from ddtrace.appsec._iast.constants import VULN_SQL_INJECTION +from ddtrace.settings.asm import config as asm_config from ddtrace.vendor import wrapt from ...contrib.dbapi import FetchTracedCursor @@ -41,6 +44,9 @@ def patch(): sqlite3.connect = wrapped sqlite3.dbapi2.connect = wrapped + if asm_config._iast_enabled: + _set_metric_iast_instrumented_sink(VULN_SQL_INJECTION) + def unpatch(): sqlite3.connect = _connect diff --git a/ddtrace/contrib/urllib3/patch.py b/ddtrace/contrib/urllib3/patch.py index 5a46b5f0e74..900d3aabf7e 100644 --- a/ddtrace/contrib/urllib3/patch.py +++ b/ddtrace/contrib/urllib3/patch.py @@ -4,9 +4,12 @@ from ddtrace import config from ddtrace.appsec._common_module_patches import wrapped_request_D8CB81E472AF98A2 as _wrap_request +from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_sink +from ddtrace.appsec._iast.constants import VULN_SSRF from ddtrace.internal.constants import COMPONENT from ddtrace.internal.schema.span_attribute_schema import SpanDirection from ddtrace.pin import Pin +from ddtrace.settings.asm import config as asm_config from ddtrace.vendor.wrapt import wrap_function_wrapper as _w from ...constants import ANALYTICS_SAMPLE_RATE_KEY @@ -58,6 +61,9 @@ def patch(): _w("urllib3.request", "RequestMethods.request", _wrap_request) Pin().onto(urllib3.connectionpool.HTTPConnectionPool) + if asm_config._iast_enabled: + _set_metric_iast_instrumented_sink(VULN_SSRF) + def unpatch(): """Disable trace for all urllib3 requests""" diff --git a/tests/appsec/iast/test_telemetry.py b/tests/appsec/iast/test_telemetry.py index 460ba0467a8..2dfe1f11026 100644 --- a/tests/appsec/iast/test_telemetry.py +++ b/tests/appsec/iast/test_telemetry.py @@ -12,7 +12,17 @@ from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import origin_to_str from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast.constants import VULN_CMDI +from ddtrace.appsec._iast.constants import VULN_HEADER_INJECTION +from ddtrace.appsec._iast.constants import VULN_PATH_TRAVERSAL +from ddtrace.appsec._iast.constants import VULN_SQL_INJECTION from ddtrace.appsec._iast.taint_sinks.command_injection import patch as cmdi_patch +from ddtrace.appsec._iast.taint_sinks.header_injection import patch as header_injection_patch +from ddtrace.appsec._iast.taint_sinks.header_injection import unpatch as header_injection_unpatch +from ddtrace.appsec._iast.taint_sinks.path_traversal import patch as path_traversal_patch +from ddtrace.appsec._iast.taint_sinks.path_traversal import unpatch_iast as path_traversal_unpatch +from ddtrace.contrib.sqlalchemy import patch as sqli_sqlalchemy_patch +from ddtrace.contrib.sqlite3 import patch as sqli_sqlite3_patch from ddtrace.ext import SpanTypes from ddtrace.internal.telemetry.constants import TELEMETRY_NAMESPACE_TAG_IAST from ddtrace.internal.telemetry.constants import TELEMETRY_TYPE_GENERATE_METRICS @@ -22,6 +32,16 @@ from tests.utils import override_global_config +def _assert_instrumented_sink(telemetry_writer, vuln_type): + metrics_result = telemetry_writer._namespace._metrics_data + generate_metrics = metrics_result[TELEMETRY_TYPE_GENERATE_METRICS][TELEMETRY_NAMESPACE_TAG_IAST] + assert len(generate_metrics) == 1, "Expected 1 generate_metrics" + assert [metric.name for metric in generate_metrics.values()] == ["instrumented.sink"] + assert [metric._tags for metric in generate_metrics.values()] == [(("vulnerability_type", vuln_type),)] + assert [metric._points[0][1] for metric in generate_metrics.values()] == [1] + assert [metric.metric_type for metric in generate_metrics.values()] == ["count"] + + @pytest.mark.parametrize( "lvl, env_lvl, expected_result", [ @@ -82,11 +102,47 @@ def test_metric_instrumented_cmdi(no_request_sampling, telemetry_writer): ): cmdi_patch() - metrics_result = telemetry_writer._namespace._metrics_data - generate_metrics = metrics_result[TELEMETRY_TYPE_GENERATE_METRICS][TELEMETRY_NAMESPACE_TAG_IAST] - assert [metric.name for metric in generate_metrics.values()] == ["instrumented.sink"] - assert [metric._tags for metric in generate_metrics.values()] == [(("vulnerability_type", "COMMAND_INJECTION"),)] - assert len(generate_metrics) == 1, "Expected 1 generate_metrics" + _assert_instrumented_sink(telemetry_writer, VULN_CMDI) + + +def test_metric_instrumented_path_traversal(no_request_sampling, telemetry_writer): + # We need to unpatch first because ddtrace.appsec._iast._patch_modules loads at runtime this patch function + path_traversal_unpatch() + with override_env(dict(DD_IAST_TELEMETRY_VERBOSITY="INFORMATION")), override_global_config( + dict(_iast_enabled=True) + ): + path_traversal_patch() + + _assert_instrumented_sink(telemetry_writer, VULN_PATH_TRAVERSAL) + + +def test_metric_instrumented_header_injection(no_request_sampling, telemetry_writer): + # We need to unpatch first because ddtrace.appsec._iast._patch_modules loads at runtime this patch function + header_injection_unpatch() + with override_env(dict(DD_IAST_TELEMETRY_VERBOSITY="INFORMATION")), override_global_config( + dict(_iast_enabled=True) + ): + header_injection_patch() + + _assert_instrumented_sink(telemetry_writer, VULN_HEADER_INJECTION) + + +def test_metric_instrumented_sqli_sqlite3(no_request_sampling, telemetry_writer): + with override_env(dict(DD_IAST_TELEMETRY_VERBOSITY="INFORMATION")), override_global_config( + dict(_iast_enabled=True) + ): + sqli_sqlite3_patch() + + _assert_instrumented_sink(telemetry_writer, VULN_SQL_INJECTION) + + +def test_metric_instrumented_sqli_sqlalchemy_patch(no_request_sampling, telemetry_writer): + with override_env(dict(DD_IAST_TELEMETRY_VERBOSITY="INFORMATION")), override_global_config( + dict(_iast_enabled=True) + ): + sqli_sqlalchemy_patch() + + _assert_instrumented_sink(telemetry_writer, VULN_SQL_INJECTION) def test_metric_instrumented_propagation(no_request_sampling, telemetry_writer): From 069d81003a680381911cc052f5e729714dfd4e4c Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Mon, 13 May 2024 17:31:56 +0200 Subject: [PATCH 035/104] fix(iast): ensure no side-effects in IAST encode aspect (#9244) ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../appsec/_iast/_taint_tracking/__init__.py | 2 +- .../appsec/iast/aspects/test_side_effects.py | 71 ++++ tests/appsec/iast/iast_utils_side_effects.py | 310 ++++++++++++++++++ 3 files changed, 382 insertions(+), 1 deletion(-) create mode 100644 tests/appsec/iast/aspects/test_side_effects.py create mode 100644 tests/appsec/iast/iast_utils_side_effects.py diff --git a/ddtrace/appsec/_iast/_taint_tracking/__init__.py b/ddtrace/appsec/_iast/_taint_tracking/__init__.py index b155e7c08a9..afada1d72d2 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/__init__.py +++ b/ddtrace/appsec/_iast/_taint_tracking/__init__.py @@ -123,7 +123,7 @@ def iast_taint_log_error(msg): def is_pyobject_tainted(pyobject: Any) -> bool: - if not pyobject or not isinstance(pyobject, IAST.TEXT_TYPES): + if not isinstance(pyobject, IAST.TEXT_TYPES): return False try: diff --git a/tests/appsec/iast/aspects/test_side_effects.py b/tests/appsec/iast/aspects/test_side_effects.py new file mode 100644 index 00000000000..c8a8f833043 --- /dev/null +++ b/tests/appsec/iast/aspects/test_side_effects.py @@ -0,0 +1,71 @@ +import pytest + +import ddtrace.appsec._iast._taint_tracking.aspects as ddtrace_aspects +from tests.appsec.iast.aspects.conftest import _iast_patched_module +from tests.appsec.iast.iast_utils_side_effects import MagicMethodsException + + +mod = _iast_patched_module("tests.appsec.iast.fixtures.aspects.str_methods") + + +def test_radd_aspect_side_effects(): + string_to_taint = "abc" + object_with_side_effects = MagicMethodsException(string_to_taint) + + def __radd__(self, a): + print(self) + print(a) + return self._data + a + + _old_method = getattr(MagicMethodsException, "__radd__", None) + setattr(MagicMethodsException, "__radd__", __radd__) + result = "123" + object_with_side_effects + assert result == string_to_taint + "123" + + result_tainted = ddtrace_aspects.add_aspect("123", object_with_side_effects) + + assert result_tainted == result + + setattr(MagicMethodsException, "__radd__", _old_method) + + +def test_add_aspect_side_effects(): + string_to_taint = "abc" + object_with_side_effects = MagicMethodsException(string_to_taint) + + def __add__(self, a): + return self._data + a + + _old_method = getattr(MagicMethodsException, "__add__", None) + setattr(MagicMethodsException, "__add__", __add__) + result = object_with_side_effects + "123" + assert result == "abc123" + + result_tainted = ddtrace_aspects.add_aspect(object_with_side_effects, "123") + + assert result_tainted == result + + setattr(MagicMethodsException, "__radd__", _old_method) + + +def test_join_aspect_side_effects(): + string_to_taint = "abc" + object_with_side_effects = MagicMethodsException(string_to_taint) + + it = ["a", "b", "c"] + + result = mod.do_join(object_with_side_effects, it) + assert result == "abcabc" + + +def test_encode_aspect_side_effects(): + string_to_taint = "abc" + object_with_side_effects = MagicMethodsException(string_to_taint) + + result = mod.do_encode(object_with_side_effects) + assert result == b"abc" + + +def test_encode_aspect_side_effects_none(): + with pytest.raises(AttributeError, match="'NoneType' object has no attribute 'encode'"): + mod.do_encode(None) diff --git a/tests/appsec/iast/iast_utils_side_effects.py b/tests/appsec/iast/iast_utils_side_effects.py new file mode 100644 index 00000000000..f55db31ed06 --- /dev/null +++ b/tests/appsec/iast/iast_utils_side_effects.py @@ -0,0 +1,310 @@ +class MagicMethodsException: + _data = "abc" + + def join(self, iterable): + return self._data + "".join(iterable) + + def encode(self, *args, **kwargs): + return self._data.encode(*args, **kwargs) + + def __init__(self, data): + self._data = data + + # We need those magic methods to verify the test with assert result = XXX + # def __new__(cls, *args, **kwargs): + # return cls + # def __setattr__(self, name, value): + # raise Exception("side effect") + # def __str__(self): + # raise Exception("side effect") + # def __repr__(self): + # raise Exception("side effect") + # def __getattribute__(self, name): + # raise Exception("side effect") + # def __getattr__(self, name): + # raise Exception("side effect") + + def __delattr__(self, name): + raise Exception("side effect") + + def __getitem__(self, key): + raise Exception("side effect") + + def __setitem__(self, key, value): + raise Exception("side effect") + + def __delitem__(self, key): + raise Exception("side effect") + + def __iter__(self): + raise Exception("side effect") + + def __next__(self): + raise Exception("side effect") + + def __contains__(self, item): + raise Exception("side effect") + + def __len__(self): + raise Exception("side effect") + + def __call__(self, *args, **kwargs): + raise Exception("side effect") + + def __enter__(self): + raise Exception("side effect") + + def __exit__(self, exc_type, exc_value, traceback): + raise Exception("side effect") + + def __await__(self): + raise Exception("side effect") + + def __aiter__(self): + raise Exception("side effect") + + async def __anext__(self): + raise Exception("side effect") + + def __bytes__(self): + raise Exception("side effect") + + def __format__(self, format_spec): + raise Exception("side effect") + + def __lt__(self, other): + raise Exception("side effect") + + def __le__(self, other): + raise Exception("side effect") + + def __eq__(self, other): + raise Exception("side effect") + + def __ne__(self, other): + raise Exception("side effect") + + def __gt__(self, other): + raise Exception("side effect") + + def __ge__(self, other): + raise Exception("side effect") + + def __hash__(self): + raise Exception("side effect") + + def __bool__(self): + raise Exception("side effect") + + def __reversed__(self): + raise Exception("side effect") + + def __abs__(self): + raise Exception("side effect") + + def __neg__(self): + raise Exception("side effect") + + def __pos__(self): + raise Exception("side effect") + + def __invert__(self): + raise Exception("side effect") + + def __round__(self, n=None): + raise Exception("side effect") + + def __floor__(self): + raise Exception("side effect") + + def __ceil__(self): + raise Exception("side effect") + + def __trunc__(self): + raise Exception("side effect") + + def __sub__(self, other): + raise Exception("side effect") + + def __mul__(self, other): + raise Exception("side effect") + + def __matmul__(self, other): + raise Exception("side effect") + + def __truediv__(self, other): + raise Exception("side effect") + + def __floordiv__(self, other): + raise Exception("side effect") + + def __mod__(self, other): + raise Exception("side effect") + + def __divmod__(self, other): + raise Exception("side effect") + + def __pow__(self, other, modulo=None): + raise Exception("side effect") + + def __lshift__(self, other): + raise Exception("side effect") + + def __rshift__(self, other): + raise Exception("side effect") + + def __and__(self, other): + raise Exception("side effect") + + def __xor__(self, other): + raise Exception("side effect") + + def __or__(self, other): + raise Exception("side effect") + + def __radd__(self, other): + raise Exception("side effect") + + def __rsub__(self, other): + raise Exception("side effect") + + def __rmul__(self, other): + raise Exception("side effect") + + def __rmatmul__(self, other): + raise Exception("side effect") + + def __rtruediv__(self, other): + raise Exception("side effect") + + def __rfloordiv__(self, other): + raise Exception("side effect") + + def __rmod__(self, other): + raise Exception("side effect") + + def __rdivmod__(self, other): + raise Exception("side effect") + + def __rpow__(self, other): + raise Exception("side effect") + + def __rlshift__(self, other): + raise Exception("side effect") + + def __rrshift__(self, other): + raise Exception("side effect") + + def __rand__(self, other): + raise Exception("side effect") + + def __rxor__(self, other): + raise Exception("side effect") + + def __ror__(self, other): + raise Exception("side effect") + + def __iadd__(self, other): + raise Exception("side effect") + + def __isub__(self, other): + raise Exception("side effect") + + def __imul__(self, other): + raise Exception("side effect") + + def __imatmul__(self, other): + raise Exception("side effect") + + def __itruediv__(self, other): + raise Exception("side effect") + + def __ifloordiv__(self, other): + raise Exception("side effect") + + def __imod__(self, other): + raise Exception("side effect") + + def __ipow__(self, other): + raise Exception("side effect") + + def __ilshift__(self, other): + raise Exception("side effect") + + def __irshift__(self, other): + raise Exception("side effect") + + def __iand__(self, other): + raise Exception("side effect") + + def __ixor__(self, other): + raise Exception("side effect") + + def __ior__(self, other): + raise Exception("side effect") + + def __dir__(self): + raise Exception("side effect") + + def __copy__(self): + raise Exception("side effect") + + def __deepcopy__(self, memo): + raise Exception("side effect") + + def __subclasshook__(self, subclass): + raise Exception("side effect") + + def __instancecheck__(self, instance): + raise Exception("side effect") + + def __subclasscheck__(self, subclass): + raise Exception("side effect") + + def __prepare__(cls, name, bases): + raise Exception("side effect") + + def __class_getitem__(self, item): + raise Exception("side effect") + + def __subclass_prepare__(self, cls): + raise Exception("side effect") + + def __init_subclass__(self, *args, **kwargs): + raise Exception("side effect") + + def __length_hint__(self): + raise Exception("side effect") + + def __missing__(self, key): + raise Exception("side effect") + + def __complex__(self): + raise Exception("side effect") + + def __int__(self): + raise Exception("side effect") + + def __float__(self): + raise Exception("side effect") + + def __index__(self): + raise Exception("side effect") + + def __del__(self): + raise Exception("side effect") + + def __getnewargs_ex__(self): + raise Exception("side effect") + + def __getnewargs__(self): + raise Exception("side effect") + + def __setstate__(self, state): + raise Exception("side effect") + + def __dict__(self): + raise Exception("side effect") + + def __weakref__(self): + raise Exception("side effect") From 2ac33639667f4025dee8c379a9e5c8a47ce2fc8d Mon Sep 17 00:00:00 2001 From: Juanjo Alvarez Martinez Date: Mon, 13 May 2024 18:21:56 +0200 Subject: [PATCH 036/104] chore(asm): add SSRF support for urllib.request (#9224) ## Description Add SSRF taint sink support for the stdlib `urllib.request` module. Also adds a release note that covers this feature and all the previous ones expanding SSRF support (so there is only one release note instead of 4). ## Checklist - [X] Change(s) are motivated and described in the PR description - [X] Testing strategy is described if automated tests are not included in the PR - [X] Risks are described (performance impact, potential for breakage, maintainability) - [X] Change is maintainable (easy to change, telemetry, documentation) - [X] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [X] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [X] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [X] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Signed-off-by: Juanjo Alvarez Co-authored-by: Alberto Vara --- .github/CODEOWNERS | 1 + ddtrace/appsec/_iast/taint_sinks/ssrf.py | 1 + ddtrace/contrib/urllib/__init__.py | 12 ++++++ ddtrace/contrib/urllib/patch.py | 34 +++++++++++++++ .../asm-ssrf-expanded-cc7d8abaa3f9c7dd.yaml | 5 +++ tests/.suitespec.json | 11 +++++ tests/appsec/iast/taint_sinks/test_ssrf.py | 42 +++++++++++++++++++ 7 files changed, 106 insertions(+) create mode 100644 ddtrace/contrib/urllib/__init__.py create mode 100644 ddtrace/contrib/urllib/patch.py create mode 100644 releasenotes/notes/asm-ssrf-expanded-cc7d8abaa3f9c7dd.yaml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 44659ae5dc0..2214fc4fb52 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -64,6 +64,7 @@ ddtrace/settings/asm.py @DataDog/asm-python ddtrace/contrib/subprocess/ @DataDog/asm-python ddtrace/contrib/flask_login/ @DataDog/asm-python ddtrace/contrib/webbrowser @DataDog/asm-python +ddtrace/contrib/urllib @DataDog/asm-python ddtrace/internal/_exceptions.py @DataDog/asm-python tests/appsec/ @DataDog/asm-python tests/contrib/dbapi/test_dbapi_appsec.py @DataDog/asm-python diff --git a/ddtrace/appsec/_iast/taint_sinks/ssrf.py b/ddtrace/appsec/_iast/taint_sinks/ssrf.py index 4e9a332b18a..47e51387c60 100644 --- a/ddtrace/appsec/_iast/taint_sinks/ssrf.py +++ b/ddtrace/appsec/_iast/taint_sinks/ssrf.py @@ -24,6 +24,7 @@ class SSRF(VulnerabilityBase): _FUNC_TO_URL_ARGUMENT = { "http.client.request": (1, "url"), "requests.sessions.request": (1, "url"), + "urllib.request.urlopen": (0, "url"), "urllib3._request_methods.request": (1, "url"), "urllib3.request.request": (1, "url"), "webbrowser.open": (0, "url"), diff --git a/ddtrace/contrib/urllib/__init__.py b/ddtrace/contrib/urllib/__init__.py new file mode 100644 index 00000000000..bd188c65b9a --- /dev/null +++ b/ddtrace/contrib/urllib/__init__.py @@ -0,0 +1,12 @@ +""" +Trace the standard library ``urllib.request`` library to trace +HTTP requests and detect SSRF vulnerabilities. It is enabled by default +if ``DD_IAST_ENABLED`` is set to ``True`` (for detecting sink points) and/or +``DD_ASM_ENABLED`` is set to ``True`` (for exploit prevention). +""" +from .patch import get_version +from .patch import patch +from .patch import unpatch + + +__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/urllib/patch.py b/ddtrace/contrib/urllib/patch.py new file mode 100644 index 00000000000..0c14f0b5318 --- /dev/null +++ b/ddtrace/contrib/urllib/patch.py @@ -0,0 +1,34 @@ +import urllib.request + +from ddtrace.appsec._common_module_patches import wrapped_request_D8CB81E472AF98A2 as _wrap_open +from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_sink +from ddtrace.appsec._iast.constants import VULN_SSRF +from ddtrace.settings.asm import config as asm_config +from ddtrace.vendor.wrapt import wrap_function_wrapper as _w + +from ..trace_utils import unwrap as _u + + +def get_version(): + # type: () -> str + return "" + + +def patch(): + """patch the built-in urllib.request methods for tracing""" + if getattr(urllib.request, "__datadog_patch", False): + return + urllib.request.__datadog_patch = True + + _w("urllib.request", "urlopen", _wrap_open) + if asm_config._iast_enabled: + _set_metric_iast_instrumented_sink(VULN_SSRF) + + +def unpatch(): + """unpatch any previously patched modules""" + if not getattr(urllib.request, "__datadog_patch", False): + return + urllib.request.__datadog_patch = False + + _u(urllib.request, "urlopen") diff --git a/releasenotes/notes/asm-ssrf-expanded-cc7d8abaa3f9c7dd.yaml b/releasenotes/notes/asm-ssrf-expanded-cc7d8abaa3f9c7dd.yaml new file mode 100644 index 00000000000..29dbdd995b6 --- /dev/null +++ b/releasenotes/notes/asm-ssrf-expanded-cc7d8abaa3f9c7dd.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Expand SSRF vulnerability support for Code Security and Exploit Prevention for the modules ``urllib3``, ``http.client``, + ``webbrowser`` and ``urllib.request``. diff --git a/tests/.suitespec.json b/tests/.suitespec.json index e11b5e8b94e..ca8921d3bb6 100644 --- a/tests/.suitespec.json +++ b/tests/.suitespec.json @@ -265,6 +265,9 @@ "webbrowser": [ "ddtrace/contrib/webbrowser/*" ], + "urllib": [ + "ddtrace/contrib/urllib/*" + ], "rq": [ "ddtrace/contrib/rq/*" ], @@ -1298,6 +1301,14 @@ "@webbrowser", "tests/appsec/iast/taint_sinks/test_ssrf.py" ], + "urllib": [ + "@bootstrap", + "@core", + "@contrib", + "@tracing", + "@urllib", + "tests/appsec/iast/taint_sinks/test_ssrf.py" + ], "vertica": [ "@bootstrap", "@core", diff --git a/tests/appsec/iast/taint_sinks/test_ssrf.py b/tests/appsec/iast/taint_sinks/test_ssrf.py index a01c38dffa9..bbc2e3a405b 100644 --- a/tests/appsec/iast/taint_sinks/test_ssrf.py +++ b/tests/appsec/iast/taint_sinks/test_ssrf.py @@ -10,6 +10,8 @@ from ddtrace.contrib.httplib.patch import unpatch as httplib_unpatch from ddtrace.contrib.requests.patch import patch as requests_patch from ddtrace.contrib.requests.patch import unpatch as requests_unpatch +from ddtrace.contrib.urllib.patch import patch as urllib_patch +from ddtrace.contrib.urllib.patch import unpatch as urllib_unpatch from ddtrace.contrib.urllib3.patch import patch as urllib3_patch from ddtrace.contrib.urllib3.patch import unpatch as urllib3_unpatch from ddtrace.contrib.webbrowser.patch import patch as webbrowser_patch @@ -142,6 +144,26 @@ def test_ssrf_webbrowser(tracer, iast_span_defaults): webbrowser_unpatch() +def test_urllib_request(tracer, iast_span_defaults): + with override_global_config(dict(_iast_enabled=True)): + urllib_patch() + try: + import urllib.request + + tainted_url, tainted_path = _get_tainted_url() + try: + # label test_urllib_request + urllib.request.urlopen(tainted_url) + except urllib.error.URLError: + pass + + span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + assert span_report + _check_report(span_report, tainted_path, "test_urllib_request") + finally: + urllib_unpatch() + + def _check_no_report_if_deduplicated(span_report, num_vuln_expected): if num_vuln_expected == 0: assert span_report is None @@ -232,3 +254,23 @@ def test_ssrf_webbrowser_deduplication(num_vuln_expected, tracer, iast_span_dedu _check_no_report_if_deduplicated(span_report, num_vuln_expected) finally: webbrowser_unpatch() + + +@pytest.mark.parametrize("num_vuln_expected", [1, 0, 0]) +def test_ssrf_urllib_deduplication(num_vuln_expected, tracer, iast_span_deduplication_enabled): + urllib_patch() + try: + import urllib.request + + tainted_url, tainted_path = _get_tainted_url() + for _ in range(0, 5): + try: + # label test_urllib_request_deduplication + urllib.request.urlopen(tainted_url) + except urllib.error.URLError: + pass + + span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_deduplication_enabled) + _check_no_report_if_deduplicated(span_report, num_vuln_expected) + finally: + urllib_unpatch() From a85ba9067bed1a37219c9ee13091f44f6bc4afda Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Mon, 13 May 2024 14:15:22 -0400 Subject: [PATCH 037/104] chore(ci): convert serverless slow import test to a subprocess test (#9236) This test was messing with the global state of `sys.modules`. It was causing some modules to get unloaded causing them to get re-imported, which would cause issues. Moving this test to a subprocess test will help isolate the behavior it is trying to test, and simplifies the test setup. ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [ ] Title is accurate - [ ] All changes are related to the pull request's stated goal - [ ] Description motivates each change - [ ] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [ ] Testing strategy adequately addresses listed risks - [ ] Change is maintainable (easy to change, telemetry, documentation) - [ ] Release note makes sense to a user of the library - [ ] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [ ] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- tests/internal/test_serverless.py | 40 ++++++++++++++----------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/tests/internal/test_serverless.py b/tests/internal/test_serverless.py index 2eb4941ada8..40301768545 100644 --- a/tests/internal/test_serverless.py +++ b/tests/internal/test_serverless.py @@ -1,6 +1,5 @@ -import sys - import mock +import pytest from ddtrace.internal.serverless import in_azure_function_consumption_plan from ddtrace.internal.serverless import in_gcp_function @@ -90,10 +89,22 @@ def test_not_azure_function_consumption_plan_wrong_sku(): assert in_azure_function_consumption_plan() is False -def test_slow_imports(monkeypatch): +# DEV: Run this test in a subprocess to avoid messing with global sys.modules state +@pytest.mark.subprocess() +def test_slow_imports(): # We should lazy load certain modules to avoid slowing down the startup # time when running in a serverless environment. This test will fail if # any of those modules are imported during the import of ddtrace. + import os + import sys + + os.environ.update( + { + "AWS_LAMBDA_FUNCTION_NAME": "foobar", + "DD_INSTRUMENTATION_TELEMETRY_ENABLED": "False", + "DD_API_SECURITY_ENABLED": "False", + } + ) blocklist = [ "ddtrace.appsec._api_security.api_manager", @@ -106,9 +117,6 @@ def test_slow_imports(monkeypatch): "importlib.metadata", "importlib_metadata", ] - monkeypatch.setenv("DD_INSTRUMENTATION_TELEMETRY_ENABLED", False) - monkeypatch.setenv("DD_API_SECURITY_ENABLED", False) - monkeypatch.setenv("AWS_LAMBDA_FUNCTION_NAME", "foobar") class BlockListFinder: def find_spec(self, fullname, *args): @@ -117,20 +125,8 @@ def find_spec(self, fullname, *args): raise ImportError(f"module {fullname} was imported!") return None - meta_path = [BlockListFinder()] - meta_path.extend(sys.meta_path) - - deleted_modules = {} - - for mod in sys.modules.copy(): - if mod.startswith("ddtrace") or mod in blocklist: - deleted_modules[mod] = sys.modules[mod] - del sys.modules[mod] - - with mock.patch("sys.meta_path", meta_path): - import ddtrace - import ddtrace.contrib.aws_lambda # noqa:F401 - import ddtrace.contrib.psycopg # noqa:F401 + sys.meta_path.insert(0, BlockListFinder()) - for name, mod in deleted_modules.items(): - sys.modules[name] = mod + import ddtrace + import ddtrace.contrib.aws_lambda # noqa:F401 + import ddtrace.contrib.psycopg # noqa:F401 From de5efd3839c6af2bd0265fee65a227bad5686d4f Mon Sep 17 00:00:00 2001 From: Juanjo Alvarez Martinez Date: Mon, 13 May 2024 21:33:46 +0200 Subject: [PATCH 038/104] fix(asm): workaround disabling builtins.open aspects (#9247) --- ddtrace/appsec/_iast/_ast/ast_patching.py | 1 + ddtrace/appsec/_iast/_ast/visitor.py | 3 ++- .../notes/asm-open-disabled-090eefa08b8b69a4.yaml | 4 ++++ tests/appsec/iast/taint_sinks/test_path_traversal.py | 8 ++++++++ tests/appsec/iast/test_iast_propagation_path.py | 12 ++++++++++++ 5 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/asm-open-disabled-090eefa08b8b69a4.yaml diff --git a/ddtrace/appsec/_iast/_ast/ast_patching.py b/ddtrace/appsec/_iast/_ast/ast_patching.py index a73ca7f4405..e27f2181644 100644 --- a/ddtrace/appsec/_iast/_ast/ast_patching.py +++ b/ddtrace/appsec/_iast/_ast/ast_patching.py @@ -30,6 +30,7 @@ "Crypto", # This module is patched by the IAST patch methods, propagation is not needed "api_pb2", # Patching crashes with these auto-generated modules, propagation is not needed "api_pb2_grpc", # ditto + "unittest.mock", ) diff --git a/ddtrace/appsec/_iast/_ast/visitor.py b/ddtrace/appsec/_iast/_ast/visitor.py index 003c4e3a465..2da7bbdcda2 100644 --- a/ddtrace/appsec/_iast/_ast/visitor.py +++ b/ddtrace/appsec/_iast/_ast/visitor.py @@ -145,7 +145,8 @@ def __init__( "definitions_module": "ddtrace.appsec._iast.taint_sinks", "alias_module": "ddtrace_taint_sinks", "functions": { - "open": "ddtrace_taint_sinks.open_path_traversal", + # FIXME: disabled to unblock release 2.9 + # "open": "ddtrace_taint_sinks.open_path_traversal", }, } self._sinkpoints_functions = self._sinkpoints_spec["functions"] diff --git a/releasenotes/notes/asm-open-disabled-090eefa08b8b69a4.yaml b/releasenotes/notes/asm-open-disabled-090eefa08b8b69a4.yaml new file mode 100644 index 00000000000..ac50fb34da8 --- /dev/null +++ b/releasenotes/notes/asm-open-disabled-090eefa08b8b69a4.yaml @@ -0,0 +1,4 @@ +--- +issues: + - | + Code Security: Security tracing for the ``builtins.open`` function is experimental and may not be stable. This aspect is not replaced by default. diff --git a/tests/appsec/iast/taint_sinks/test_path_traversal.py b/tests/appsec/iast/taint_sinks/test_path_traversal.py index 0dda76950e7..5f5abaaa9c9 100644 --- a/tests/appsec/iast/taint_sinks/test_path_traversal.py +++ b/tests/appsec/iast/taint_sinks/test_path_traversal.py @@ -23,6 +23,8 @@ def _get_path_traversal_module_functions(): yield module, function +# FIXME: enable once the mock + open issue is fixed +@pytest.mark.skip def test_path_traversal_open(iast_span_defaults): mod = _iast_patched_module("tests.appsec.iast.fixtures.taint_sinks.path_traversal") @@ -49,6 +51,8 @@ def test_path_traversal_open(iast_span_defaults): assert vulnerability["evidence"].get("redacted") is None +# FIXME: enable once the mock + open issue is fixed +@pytest.mark.skip @pytest.mark.parametrize( "file_path", ( @@ -70,6 +74,8 @@ def test_path_traversal_open_secure(file_path, iast_span_defaults): assert span_report is None +# FIXME: enable once the mock + open issue is fixed +@pytest.mark.skip @pytest.mark.parametrize( "module, function", _get_path_traversal_module_functions(), @@ -103,6 +109,8 @@ def test_path_traversal(module, function, iast_span_defaults): assert vulnerability["evidence"].get("redacted") is None +# FIXME: enable once the mock + open issue is fixed +@pytest.mark.skip @pytest.mark.parametrize("num_vuln_expected", [1, 0, 0]) def test_path_traversal_deduplication(num_vuln_expected, iast_span_deduplication_enabled): mod = _iast_patched_module("tests.appsec.iast.fixtures.taint_sinks.path_traversal") diff --git a/tests/appsec/iast/test_iast_propagation_path.py b/tests/appsec/iast/test_iast_propagation_path.py index 9637b692501..f683b262eb1 100644 --- a/tests/appsec/iast/test_iast_propagation_path.py +++ b/tests/appsec/iast/test_iast_propagation_path.py @@ -27,6 +27,8 @@ def _assert_vulnerability(data, value_parts, file_line_label): assert vulnerability["hash"] == hash_value +# FIXME: enable once the mock + open issue is fixed +@pytest.mark.skip def test_propagation_no_path(iast_span_defaults): mod = _iast_patched_module("tests.appsec.iast.fixtures.propagation_path") origin1 = "taintsource" @@ -39,6 +41,8 @@ def test_propagation_no_path(iast_span_defaults): assert span_report is None +# FIXME: enable once the mock + open issue is fixed +@pytest.mark.skip @pytest.mark.parametrize( "origin1", [ @@ -73,6 +77,8 @@ def test_propagation_path_1_origin_1_propagation(origin1, iast_span_defaults): _assert_vulnerability(data, value_parts, "propagation_path_1_source_1_prop") +# FIXME: enable once the mock + open issue is fixed +@pytest.mark.skip @pytest.mark.parametrize( "origin1", [ @@ -109,6 +115,8 @@ def test_propagation_path_1_origins_2_propagations(origin1, iast_span_defaults): _assert_vulnerability(data, value_parts, "propagation_path_1_source_2_prop") +# FIXME: enable once the mock + open issue is fixed +@pytest.mark.skip @pytest.mark.parametrize( "origin1, origin2", [ @@ -159,6 +167,8 @@ def test_propagation_path_2_origins_2_propagations(origin1, origin2, iast_span_d _assert_vulnerability(data, value_parts, "propagation_path_2_source_2_prop") +# FIXME: enable once the mock + open issue is fixed +@pytest.mark.skip @pytest.mark.parametrize( "origin1, origin2", [ @@ -217,6 +227,8 @@ def test_propagation_path_2_origins_3_propagation(origin1, origin2, iast_span_de _assert_vulnerability(data, value_parts, "propagation_path_3_prop") +# FIXME: enable once the mock + open issue is fixed +@pytest.mark.skip @pytest.mark.parametrize( "origin1, origin2", [ From 3e34d21cb9b5e1916e549047158cb119317b96ab Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Mon, 13 May 2024 16:53:32 -0400 Subject: [PATCH 039/104] revert(distributed_tracing): ensure last datadog parent id tag is always set (#9231) This reverts commit 22302147a2ac3baa03727c73085fd6575fba60e9. W3C-Datadog header specification has significant gaps and needs to be revised. This change is unreleased so a release note is not required. ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/internal/constants.py | 1 - ddtrace/propagation/http.py | 14 +++++--------- tests/tracer/test_propagation.py | 17 ++++++----------- 3 files changed, 11 insertions(+), 21 deletions(-) diff --git a/ddtrace/internal/constants.py b/ddtrace/internal/constants.py index 7988687a74c..50b8e1280e4 100644 --- a/ddtrace/internal/constants.py +++ b/ddtrace/internal/constants.py @@ -25,7 +25,6 @@ DEFAULT_SAMPLING_RATE_LIMIT = 100 SAMPLING_DECISION_TRACE_TAG_KEY = "_dd.p.dm" LAST_DD_PARENT_ID_KEY = "_dd.parent_id" -DEFAULT_LAST_PARENT_ID = "0000000000000000" DEFAULT_SERVICE_NAME = "unnamed-python-service" # Used to set the name of an integration on a span COMPONENT = "component" diff --git a/ddtrace/propagation/http.py b/ddtrace/propagation/http.py index 6ad0c86d41d..c0768194d8b 100644 --- a/ddtrace/propagation/http.py +++ b/ddtrace/propagation/http.py @@ -38,7 +38,6 @@ from ..internal.compat import ensure_text from ..internal.constants import _PROPAGATION_STYLE_NONE from ..internal.constants import _PROPAGATION_STYLE_W3C_TRACECONTEXT -from ..internal.constants import DEFAULT_LAST_PARENT_ID from ..internal.constants import HIGHER_ORDER_TRACE_ID_BITS as _HIGHER_ORDER_TRACE_ID_BITS from ..internal.constants import LAST_DD_PARENT_ID_KEY from ..internal.constants import MAX_UINT_64BITS as _MAX_UINT_64BITS @@ -701,7 +700,7 @@ def _get_traceparent_values(tp): @staticmethod def _get_tracestate_values(ts_l): - # type: (List[str]) -> Tuple[Optional[int], Dict[str, str], Optional[str], str] + # type: (List[str]) -> Tuple[Optional[int], Dict[str, str], Optional[str], Optional[str]] # tracestate list parsing example: ["dd=s:2;o:rum;t.dm:-4;t.usr.id:baz64","congo=t61rcWkgMzE"] # -> 2, {"_dd.p.dm":"-4","_dd.p.usr.id":"baz64"}, "rum" @@ -728,7 +727,7 @@ def _get_tracestate_values(ts_l): origin = _TraceContext.decode_tag_val(origin) # Get last datadog parent id, this field is used to reconnect traces with missing spans - lpid = dd.get("p", DEFAULT_LAST_PARENT_ID) + lpid = dd.get("p", "0000000000000000") # need to convert from t. to _dd.p. other_propagated_tags = { @@ -737,7 +736,7 @@ def _get_tracestate_values(ts_l): return sampling_priority_ts_int, other_propagated_tags, origin, lpid else: - return None, {}, None, DEFAULT_LAST_PARENT_ID + return None, {}, None, None @staticmethod def _get_sampling_priority( @@ -821,7 +820,8 @@ def _get_context(trace_id, span_id, trace_flag, ts, meta=None): if tracestate_values: sampling_priority_ts, other_propagated_tags, origin, lpid = tracestate_values meta.update(other_propagated_tags.items()) - meta[LAST_DD_PARENT_ID_KEY] = lpid + if lpid: + meta[LAST_DD_PARENT_ID_KEY] = lpid sampling_priority = _TraceContext._get_sampling_priority(trace_flag, sampling_priority_ts, origin) else: @@ -921,10 +921,6 @@ def _resolve_contexts(contexts, styles_w_ctx, normalized_headers): ts = _extract_header_value(_POSSIBLE_HTTP_HEADER_TRACESTATE, normalized_headers) if ts: primary_context._meta[W3C_TRACESTATE_KEY] = ts - # Ensure the last datadog parent id is always set on the primary context - primary_context._meta[LAST_DD_PARENT_ID_KEY] = context._meta.get( - LAST_DD_PARENT_ID_KEY, DEFAULT_LAST_PARENT_ID - ) primary_context._span_links = links return primary_context diff --git a/tests/tracer/test_propagation.py b/tests/tracer/test_propagation.py index 4c3071e61b0..e6c263e5d83 100644 --- a/tests/tracer/test_propagation.py +++ b/tests/tracer/test_propagation.py @@ -692,7 +692,7 @@ def test_get_wsgi_header(tracer): # noqa: F811 TRACECONTEXT_HEADERS_VALID_64_bit = { _HTTP_HEADER_TRACEPARENT: "00-000000000000000064fe8b2a57d3eff7-00f067aa0ba902b7-01", - _HTTP_HEADER_TRACESTATE: "dd=s:2;o:rum;p:00f067aa0ba902b7;t.dm:-4;t.usr.id:baz64,congo=t61rcWkgMzE", + _HTTP_HEADER_TRACESTATE: "dd=s:2;o:rum;t.dm:-4;t.usr.id:baz64,congo=t61rcWkgMzE", } @@ -937,7 +937,7 @@ def test_extract_traceparent(caplog, headers, expected_tuple, expected_logging, ( "congo=t61rcWkgMzE,mako=s:2;o:rum;", # sampling_priority_ts, other_propagated_tags, origin, parent id - (None, {}, None, "0000000000000000"), + (None, {}, None, None), None, None, ), @@ -1754,11 +1754,7 @@ def test_extract_tracecontext(headers, expected_context): "span_id": 5678, "sampling_priority": 1, "dd_origin": "synthetics", - "meta": { - "tracestate": DATADOG_TRACECONTEXT_MATCHING_TRACE_ID_HEADERS[_HTTP_HEADER_TRACESTATE], - "_dd.p.dm": "-3", - LAST_DD_PARENT_ID_KEY: "00f067aa0ba902b7", - }, + "meta": {"tracestate": TRACECONTEXT_HEADERS_VALID[_HTTP_HEADER_TRACESTATE], "_dd.p.dm": "-3"}, }, ), # testing that tracestate is not added when tracecontext style comes later and does not match first style's trace-id @@ -2006,11 +2002,11 @@ def test_DD_TRACE_PROPAGATION_STYLE_EXTRACT_overrides_DD_TRACE_PROPAGATION_STYLE span_id=67667974448284343, meta={ "traceparent": "00-000000000000000064fe8b2a57d3eff7-00f067aa0ba902b7-01", - "tracestate": ALL_HEADERS_CHAOTIC_2[_HTTP_HEADER_TRACESTATE], + "tracestate": TRACECONTEXT_HEADERS_VALID[_HTTP_HEADER_TRACESTATE], "_dd.p.dm": "-4", "_dd.p.usr.id": "baz64", "_dd.origin": "rum", - LAST_DD_PARENT_ID_KEY: "00f067aa0ba902b7", + LAST_DD_PARENT_ID_KEY: "0000000000000000", }, metrics={"_sampling_priority_v1": 2}, span_links=[ @@ -2121,8 +2117,7 @@ def test_DD_TRACE_PROPAGATION_STYLE_EXTRACT_overrides_DD_TRACE_PROPAGATION_STYLE meta={ "_dd.p.dm": "-3", "_dd.origin": "synthetics", - "tracestate": ALL_HEADERS_CHAOTIC_1[_HTTP_HEADER_TRACESTATE], - LAST_DD_PARENT_ID_KEY: "00f067aa0ba902b7", + "tracestate": "dd=s:2;o:rum;t.dm:-4;t.usr.id:baz64,congo=t61rcWkgMzE", }, metrics={"_sampling_priority_v1": 1}, span_links=[ From f68282623863e9a8667afb33e5c5cb0597ee4305 Mon Sep 17 00:00:00 2001 From: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> Date: Tue, 14 May 2024 11:05:50 +0200 Subject: [PATCH 040/104] feat(asm): enable exploit prevention (#9246) - Enable Exploit Prevention by default (opt out) - Add span metrics tag for rasp duration and evaluations - Improve unit tests for rasp to check for span metrics ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [ ] Title is accurate - [ ] All changes are related to the pull request's stated goal - [ ] Description motivates each change - [ ] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [ ] Testing strategy adequately addresses listed risks - [ ] Change is maintainable (easy to change, telemetry, documentation) - [ ] Release note makes sense to a user of the library - [ ] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [ ] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/appsec/_asm_request_context.py | 37 +++++++++++++++++-- ddtrace/appsec/_constants.py | 3 ++ ddtrace/appsec/_metrics.py | 2 +- ddtrace/appsec/_processor.py | 15 +------- ddtrace/settings/asm.py | 2 +- ...t_prevention_enabled-ae26036621f6140c.yaml | 7 ++++ tests/appsec/contrib_appsec/conftest.py | 5 +++ tests/appsec/contrib_appsec/utils.py | 7 ++++ .../django/test_django_appsec_snapshots.py | 15 ++++++++ 9 files changed, 74 insertions(+), 19 deletions(-) create mode 100644 releasenotes/notes/exploit_prevention_enabled-ae26036621f6140c.yaml diff --git a/ddtrace/appsec/_asm_request_context.py b/ddtrace/appsec/_asm_request_context.py index 6e03dae6bb4..ae8597300c9 100644 --- a/ddtrace/appsec/_asm_request_context.py +++ b/ddtrace/appsec/_asm_request_context.py @@ -8,6 +8,7 @@ from typing import List from typing import Optional from typing import Set +from typing import Union from urllib import parse from ddtrace._trace.span import Span @@ -109,9 +110,15 @@ def unregister(span: Span) -> None: env.must_call_globals = False +def update_span_metrics(span: Span, name: str, value: Union[float, int]) -> None: + span.set_metric(name, value + (span.get_metric(name) or 0.0)) + + def flush_waf_triggers(env: ASM_Environment) -> None: - if env.waf_triggers and env.span: - root_span = env.span._local_root or env.span + if not env.span: + return + root_span = env.span._local_root or env.span + if env.waf_triggers: report_list = get_triggers(root_span) if report_list is not None: report_list.extend(env.waf_triggers) @@ -122,6 +129,18 @@ def flush_waf_triggers(env: ASM_Environment) -> None: else: root_span.set_tag(APPSEC.JSON, json.dumps({"triggers": report_list}, separators=(",", ":"))) env.waf_triggers = [] + telemetry_results = get_value(_TELEMETRY, _TELEMETRY_WAF_RESULTS) + if telemetry_results: + from ddtrace.appsec._metrics import DDWAF_VERSION + + root_span.set_tag_str(APPSEC.WAF_VERSION, DDWAF_VERSION) + if telemetry_results["total_duration"]: + update_span_metrics(root_span, APPSEC.WAF_DURATION, telemetry_results["duration"]) + update_span_metrics(root_span, APPSEC.WAF_DURATION_EXT, telemetry_results["total_duration"]) + if telemetry_results["rasp"]["sum_eval"]: + update_span_metrics(root_span, APPSEC.RASP_DURATION, telemetry_results["rasp"]["duration"]) + update_span_metrics(root_span, APPSEC.RASP_DURATION_EXT, telemetry_results["rasp"]["total_duration"]) + update_span_metrics(root_span, APPSEC.RASP_RULE_EVAL, telemetry_results["rasp"]["sum_eval"]) GLOBAL_CALLBACKS[_CONTEXT_CALL] = [flush_waf_triggers] @@ -150,8 +169,12 @@ def __init__(self): "triggered": False, "timeout": False, "version": None, + "duration": 0.0, + "total_duration": 0.0, "rasp": { - "called": False, + "sum_eval": 0, + "duration": 0.0, + "total_duration": 0.0, "eval": {t: 0 for _, t in EXPLOIT_PREVENTION.TYPE}, "match": {t: 0 for _, t in EXPLOIT_PREVENTION.TYPE}, "timeout": {t: 0 for _, t in EXPLOIT_PREVENTION.TYPE}, @@ -344,6 +367,8 @@ def set_waf_telemetry_results( is_blocked: bool, is_timeout: bool, rule_type: Optional[str], + duration: float, + total_duration: float, ) -> None: result = get_value(_TELEMETRY, _TELEMETRY_WAF_RESULTS) if result is not None: @@ -354,12 +379,16 @@ def set_waf_telemetry_results( result["timeout"] |= is_timeout if rules_version is not None: result["version"] = rules_version + result["duration"] += duration + result["total_duration"] += total_duration else: # Exploit Prevention telemetry - result["rasp"]["called"] = True + result["rasp"]["sum_eval"] += 1 result["rasp"]["eval"][rule_type] += 1 result["rasp"]["match"][rule_type] += int(is_triggered) result["rasp"]["timeout"][rule_type] += int(is_timeout) + result["rasp"]["duration"] += duration + result["rasp"]["total_duration"] += total_duration def get_waf_telemetry_results() -> Optional[Dict[str, Any]]: diff --git a/ddtrace/appsec/_constants.py b/ddtrace/appsec/_constants.py index 59f90a335dc..5ce16dd3130 100644 --- a/ddtrace/appsec/_constants.py +++ b/ddtrace/appsec/_constants.py @@ -53,6 +53,9 @@ class APPSEC(metaclass=Constant_Class): WAF_DURATION_EXT = "_dd.appsec.waf.duration_ext" WAF_TIMEOUTS = "_dd.appsec.waf.timeouts" WAF_VERSION = "_dd.appsec.waf.version" + RASP_DURATION = "_dd.appsec.rasp.duration" + RASP_DURATION_EXT = "_dd.appsec.rasp.duration_ext" + RASP_RULE_EVAL = "_dd.appsec.rasp.rule.eval" ORIGIN_VALUE = "appsec" CUSTOM_EVENT_PREFIX = "appsec.events" USER_LOGIN_EVENT_PREFIX = "_dd.appsec.events.users.login" diff --git a/ddtrace/appsec/_metrics.py b/ddtrace/appsec/_metrics.py index 28d712cebf7..e36212e6a93 100644 --- a/ddtrace/appsec/_metrics.py +++ b/ddtrace/appsec/_metrics.py @@ -106,7 +106,7 @@ def _set_waf_request_metrics(*args): tags=tags_request, ) rasp = result["rasp"] - if rasp["called"]: + if rasp["sum_eval"]: for t, n in [("eval", "rasp.rule.eval"), ("match", "rasp.rule.match"), ("timeout", "rasp.timeout")]: for rule_type, value in rasp[t].items(): if value: diff --git a/ddtrace/appsec/_processor.py b/ddtrace/appsec/_processor.py index a3d0518b6a4..db0b83d6eb4 100644 --- a/ddtrace/appsec/_processor.py +++ b/ddtrace/appsec/_processor.py @@ -345,6 +345,8 @@ def _waf_action( bool(blocked), waf_results.timeout, rule_type, + waf_results.runtime, + waf_results.total_runtime, ) if blocked: core.set_item(WAF_CONTEXT_NAMES.BLOCKED, blocked, span=span) @@ -357,21 +359,8 @@ def _waf_action( span.set_tag_str(APPSEC.EVENT_RULE_ERRORS, errors) log.debug("Error in ASM In-App WAF: %s", errors) span.set_tag_str(APPSEC.EVENT_RULE_VERSION, info.version) - from ddtrace.appsec._ddwaf import version - - span.set_tag_str(APPSEC.WAF_VERSION, version()) - - def update_metric(name, value): - old_value = span.get_metric(name) - if old_value is None: - old_value = 0.0 - span.set_metric(name, value + old_value) - span.set_metric(APPSEC.EVENT_RULE_LOADED, info.loaded) span.set_metric(APPSEC.EVENT_RULE_ERROR_COUNT, info.failed) - if waf_results.runtime: - update_metric(APPSEC.WAF_DURATION, waf_results.runtime) - update_metric(APPSEC.WAF_DURATION_EXT, waf_results.total_runtime) except (JSONDecodeError, ValueError): log.warning("Error parsing data ASM In-App WAF metrics report %s", info.errors) except Exception: diff --git a/ddtrace/settings/asm.py b/ddtrace/settings/asm.py index 91fb9417807..462a704a9f0 100644 --- a/ddtrace/settings/asm.py +++ b/ddtrace/settings/asm.py @@ -92,7 +92,7 @@ class ASMConfig(Env): _deduplication_enabled = Env.var(bool, "_DD_APPSEC_DEDUPLICATION_ENABLED", default=True) # default will be set to True once the feature is GA. For now it's always False - _ep_enabled = Env.var(bool, EXPLOIT_PREVENTION.EP_ENABLED, default=False) + _ep_enabled = Env.var(bool, EXPLOIT_PREVENTION.EP_ENABLED, default=True) _ep_stack_trace_enabled = Env.var(bool, EXPLOIT_PREVENTION.STACK_TRACE_ENABLED, default=True) # for max_stack_traces, 0 == unlimited _ep_max_stack_traces = Env.var( diff --git a/releasenotes/notes/exploit_prevention_enabled-ae26036621f6140c.yaml b/releasenotes/notes/exploit_prevention_enabled-ae26036621f6140c.yaml new file mode 100644 index 00000000000..2600693a8f1 --- /dev/null +++ b/releasenotes/notes/exploit_prevention_enabled-ae26036621f6140c.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + ASM: This introduces full support for exploit prevention in the python tracer. + - LFI (via standard API open) + - SSRF (via standard API urllib or third party requests) + with monitoring and blocking feature, telemetry and span metrics reports. diff --git a/tests/appsec/contrib_appsec/conftest.py b/tests/appsec/contrib_appsec/conftest.py index 65b8fcc1893..356c2fced5c 100644 --- a/tests/appsec/contrib_appsec/conftest.py +++ b/tests/appsec/contrib_appsec/conftest.py @@ -68,6 +68,11 @@ def get_tag(root_span): yield lambda name: root_span().get_tag(name) +@pytest.fixture +def get_metric(root_span): + yield lambda name: root_span().get_metric(name) + + def no_op(msg: str) -> None: # noqa: ARG001 """Do nothing.""" diff --git a/tests/appsec/contrib_appsec/utils.py b/tests/appsec/contrib_appsec/utils.py index a1f6441db56..850c3d91fa9 100644 --- a/tests/appsec/contrib_appsec/utils.py +++ b/tests/appsec/contrib_appsec/utils.py @@ -1202,6 +1202,7 @@ def test_exploit_prevention( interface, root_span, get_tag, + get_metric, asm_enabled, ep_enabled, endpoint, @@ -1215,6 +1216,7 @@ def test_exploit_prevention( from ddtrace.appsec._common_module_patches import patch_common_modules from ddtrace.appsec._common_module_patches import unpatch_common_modules + from ddtrace.appsec._constants import APPSEC from ddtrace.appsec._metrics import DDWAF_VERSION from ddtrace.contrib.requests import patch as patch_requests from ddtrace.contrib.requests import unpatch as unpatch_requests @@ -1260,6 +1262,11 @@ def test_exploit_prevention( assert get_tag("rasp.request.done") is None else: assert get_tag("rasp.request.done") == endpoint + assert get_metric(APPSEC.RASP_DURATION) is not None + assert get_metric(APPSEC.RASP_DURATION_EXT) is not None + assert get_metric(APPSEC.RASP_RULE_EVAL) is not None + assert float(get_metric(APPSEC.RASP_DURATION_EXT)) >= float(get_metric(APPSEC.RASP_DURATION)) + assert int(get_metric(APPSEC.RASP_RULE_EVAL)) > 0 else: assert get_triggers(root_span()) is None assert self.check_for_stack_trace(root_span) == [] diff --git a/tests/contrib/django/test_django_appsec_snapshots.py b/tests/contrib/django/test_django_appsec_snapshots.py index df82a47963d..bc8a1d20dcb 100644 --- a/tests/contrib/django/test_django_appsec_snapshots.py +++ b/tests/contrib/django/test_django_appsec_snapshots.py @@ -71,6 +71,9 @@ def daphne_client(django_asgi, additional_env=None): "meta_struct", "metrics._dd.appsec.waf.duration", "metrics._dd.appsec.waf.duration_ext", + "metrics._dd.appsec.rasp.duration", + "metrics._dd.appsec.rasp.duration_ext", + "metrics._dd.appsec.rasp.rule.eval", APPSEC_JSON_TAG, ] ) @@ -93,6 +96,9 @@ def test_appsec_enabled(): "meta_struct", "metrics._dd.appsec.waf.duration", "metrics._dd.appsec.waf.duration_ext", + "metrics._dd.appsec.rasp.duration", + "metrics._dd.appsec.rasp.duration_ext", + "metrics._dd.appsec.rasp.rule.eval", APPSEC_JSON_TAG, ] ) @@ -113,6 +119,9 @@ def test_appsec_enabled_attack(): "meta_struct", "metrics._dd.appsec.waf.duration", "metrics._dd.appsec.waf.duration_ext", + "metrics._dd.appsec.rasp.duration", + "metrics._dd.appsec.rasp.duration_ext", + "metrics._dd.appsec.rasp.rule.eval", APPSEC_JSON_TAG, "metrics._dd.appsec.event_rules.loaded", ] @@ -145,6 +154,9 @@ def test_request_ipblock_nomatch_200(): "meta_struct", "metrics._dd.appsec.waf.duration", "metrics._dd.appsec.waf.duration_ext", + "metrics._dd.appsec.rasp.duration", + "metrics._dd.appsec.rasp.duration_ext", + "metrics._dd.appsec.rasp.rule.eval", "metrics._dd.appsec.event_rules.loaded", ] ) @@ -182,6 +194,9 @@ def test_request_ipblock_match_403(): "meta_struct", "metrics._dd.appsec.waf.duration", "metrics._dd.appsec.waf.duration_ext", + "metrics._dd.appsec.rasp.duration", + "metrics._dd.appsec.rasp.duration_ext", + "metrics._dd.appsec.rasp.rule.eval", "metrics._dd.appsec.event_rules.loaded", ] ) From ab8651561ff45f6bd8e1e0a5eb572e32538cced0 Mon Sep 17 00:00:00 2001 From: lievan <42917263+lievan@users.noreply.github.com> Date: Tue, 14 May 2024 07:56:20 -0400 Subject: [PATCH 041/104] feat(llmobs): allow users to toggle on/off sending llmobs data (#9242) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR decouples starting/stopping LLMObs traces locally and sending trace data to the LLMObs backend. Previously these two concepts were tied together and configured through the `enable()` and `DD_LLMOBS_ENABLED` environment variable. This is problematic because it results in instrumented code to break if `DD_LLMOBS_ENABLED` is false or the `enable()` function is not explicitly called (since starting spans returns `None` if llmobs is not enabled). This is a) inconvenient when unit testing b) prevents people from disabling sending LLMObs data unless they strip out all LLMObs code from their instrumented app We want to enable LLMObs tracing locally while giving users the ability to toggle actually sending traces to our backend. The state of `DD_LLMOBS_ENABLED` and `LLMObs.enabled` variable should not impact any of the user’s code at all. It purely configures data to be sent/not sent to our backend. This can be done through: 1. Having LLMObs service always have a default `_instance` with `ddtrace.tracer` as the internal tracer. 2. Move starting the span and eval metric writer out of the `__init__` function 3. Have span and eval metric writers only be started or stopped in `enable()` and `disable()` Case to consider: When DD_LLMOBS_ENABLED is False, APM is enabled, and someone uses the SDK decorators to start/stop llm spans in this case trace processing for llmobs is turned off, and writers are turned off, yet llmobs spans are being made by the SDK and submitted to APM. ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: lievan Co-authored-by: Yun Kim <35776586+Yun-Kim@users.noreply.github.com> --- ddtrace/llmobs/_constants.py | 4 ++ ddtrace/llmobs/_integrations/base.py | 3 +- ddtrace/llmobs/_llmobs.py | 99 +++++++++++++++----------- ddtrace/llmobs/_utils.py | 2 +- ddtrace/llmobs/decorators.py | 12 ++-- tests/llmobs/test_llmobs_decorators.py | 5 +- tests/llmobs/test_llmobs_service.py | 42 +++++------ 7 files changed, 91 insertions(+), 76 deletions(-) diff --git a/ddtrace/llmobs/_constants.py b/ddtrace/llmobs/_constants.py index 9d04fa68cbf..990ab2977f2 100644 --- a/ddtrace/llmobs/_constants.py +++ b/ddtrace/llmobs/_constants.py @@ -16,3 +16,7 @@ OUTPUT_DOCUMENTS = "_ml_obs.meta.output.documents" OUTPUT_MESSAGES = "_ml_obs.meta.output.messages" OUTPUT_VALUE = "_ml_obs.meta.output.value" + +SPAN_START_WHILE_DISABLED_WARNING = ( + "Span started while LLMObs is disabled." " Spans will not be sent to LLM Observability." +) diff --git a/ddtrace/llmobs/_integrations/base.py b/ddtrace/llmobs/_integrations/base.py index f514e56846c..8e5396baa62 100644 --- a/ddtrace/llmobs/_integrations/base.py +++ b/ddtrace/llmobs/_integrations/base.py @@ -16,6 +16,7 @@ from ddtrace.internal.dogstatsd import get_dogstatsd_client from ddtrace.internal.hostname import get_hostname from ddtrace.internal.utils.formats import asbool +from ddtrace.llmobs._llmobs import LLMObs from ddtrace.llmobs._log_writer import V2LogWriter from ddtrace.sampler import RateSampler from ddtrace.settings import IntegrationConfig @@ -71,7 +72,7 @@ def logs_enabled(self) -> bool: @property def llmobs_enabled(self) -> bool: """Return whether submitting llmobs payloads is enabled.""" - return config._llmobs_enabled + return LLMObs.enabled def is_pc_sampled_span(self, span: Span) -> bool: if span.context.sampling_priority is not None: diff --git a/ddtrace/llmobs/_llmobs.py b/ddtrace/llmobs/_llmobs.py index d72aa983fe5..75393e461ae 100644 --- a/ddtrace/llmobs/_llmobs.py +++ b/ddtrace/llmobs/_llmobs.py @@ -12,6 +12,8 @@ from ddtrace.internal import atexit from ddtrace.internal.logger import get_logger from ddtrace.internal.service import Service +from ddtrace.internal.service import ServiceStatusError +from ddtrace.internal.utils.formats import asbool from ddtrace.llmobs._constants import INPUT_DOCUMENTS from ddtrace.llmobs._constants import INPUT_MESSAGES from ddtrace.llmobs._constants import INPUT_PARAMETERS @@ -26,6 +28,7 @@ from ddtrace.llmobs._constants import OUTPUT_VALUE from ddtrace.llmobs._constants import SESSION_ID from ddtrace.llmobs._constants import SPAN_KIND +from ddtrace.llmobs._constants import SPAN_START_WHILE_DISABLED_WARNING from ddtrace.llmobs._constants import TAGS from ddtrace.llmobs._trace_processor import LLMObsTraceProcessor from ddtrace.llmobs._utils import _get_ml_app @@ -41,12 +44,13 @@ class LLMObs(Service): - _instance = None + _instance = None # type: LLMObs enabled = False def __init__(self, tracer=None): super(LLMObs, self).__init__() self.tracer = tracer or ddtrace.tracer + self._llmobs_span_writer = LLMObsSpanWriter( site=config._dd_site, api_key=config._dd_api_key, @@ -59,16 +63,25 @@ def __init__(self, tracer=None): interval=float(os.getenv("_DD_LLMOBS_WRITER_INTERVAL", 1.0)), timeout=float(os.getenv("_DD_LLMOBS_WRITER_TIMEOUT", 2.0)), ) - self._llmobs_span_writer.start() - self._llmobs_eval_metric_writer.start() def _start_service(self) -> None: tracer_filters = self.tracer._filters if not any(isinstance(tracer_filter, LLMObsTraceProcessor) for tracer_filter in tracer_filters): tracer_filters += [LLMObsTraceProcessor(self._llmobs_span_writer)] self.tracer.configure(settings={"FILTERS": tracer_filters}) + try: + self._llmobs_span_writer.start() + self._llmobs_eval_metric_writer.start() + except ServiceStatusError: + log.debug("Error starting LLMObs writers") def _stop_service(self) -> None: + try: + self._llmobs_span_writer.stop() + self._llmobs_eval_metric_writer.stop() + except ServiceStatusError: + log.debug("Error stopping LLMObs writers") + try: self.tracer.shutdown() except Exception: @@ -76,40 +89,47 @@ def _stop_service(self) -> None: @classmethod def enable(cls, tracer=None): - if cls._instance is not None: + if cls.enabled: log.debug("%s already enabled", cls.__name__) return + if os.getenv("DD_LLMOBS_ENABLED") and not asbool(os.getenv("DD_LLMOBS_ENABLED")): + log.debug("LLMObs.enable() called when DD_LLMOBS_ENABLED is set to false or 0, not starting LLMObs service") + return + if not config._dd_api_key: - cls.enabled = False raise ValueError( "DD_API_KEY is required for sending LLMObs data. " "Ensure this configuration is set before running your application." ) if not config._llmobs_ml_app: - cls.enabled = False raise ValueError( "DD_LLMOBS_APP_NAME is required for sending LLMObs data. " "Ensure this configuration is set before running your application." ) + # override the default _instance with a new tracer cls._instance = cls(tracer=tracer) + cls.enabled = True + + # turn on llmobs trace processing cls._instance.start() + atexit.register(cls.disable) log.debug("%s enabled", cls.__name__) @classmethod def disable(cls) -> None: - if cls._instance is None: + if not cls.enabled: log.debug("%s not enabled", cls.__name__) return log.debug("Disabling %s", cls.__name__) atexit.unregister(cls.disable) - cls._instance.stop() - cls._instance = None cls.enabled = False + cls._instance.stop() + log.debug("%s disabled", cls.__name__) @classmethod @@ -117,9 +137,6 @@ def export_span(cls, span: Optional[Span] = None) -> Optional[ExportedLLMObsSpan """Returns a simple representation of a span to export its span and trace IDs. If no span is provided, the current active LLMObs-type span will be used. """ - if cls.enabled is False or cls._instance is None: - log.warning("LLMObs.export_span() requires LLMObs to be enabled.") - return None if span: try: if span.span_type != SpanTypes.LLM: @@ -185,14 +202,14 @@ def llm( :returns: The Span object representing the traced operation. """ - if cls.enabled is False or cls._instance is None: - log.warning("LLMObs.llm() cannot be used while LLMObs is disabled.") - return None + if cls.enabled is False: + log.warning(SPAN_START_WHILE_DISABLED_WARNING) if not model_name: - log.warning("model_name must be the specified name of the invoked model.") - return None + log.warning("LLMObs.llm() missing model_name") if model_provider is None: model_provider = "custom" + if model_name is None: + model_name = "unknown" return cls._instance._start_span( "llm", name, model_name=model_name, model_provider=model_provider, session_id=session_id, ml_app=ml_app ) @@ -211,9 +228,8 @@ def tool( :returns: The Span object representing the traced operation. """ - if cls.enabled is False or cls._instance is None: - log.warning("LLMObs.tool() cannot be used while LLMObs is disabled.") - return None + if cls.enabled is False: + log.warning(SPAN_START_WHILE_DISABLED_WARNING) return cls._instance._start_span("tool", name=name, session_id=session_id, ml_app=ml_app) @classmethod @@ -230,9 +246,8 @@ def task( :returns: The Span object representing the traced operation. """ - if cls.enabled is False or cls._instance is None: - log.warning("LLMObs.task() cannot be used while LLMObs is disabled.") - return None + if cls.enabled is False: + log.warning(SPAN_START_WHILE_DISABLED_WARNING) return cls._instance._start_span("task", name=name, session_id=session_id, ml_app=ml_app) @classmethod @@ -249,9 +264,8 @@ def agent( :returns: The Span object representing the traced operation. """ - if cls.enabled is False or cls._instance is None: - log.warning("LLMObs.agent() cannot be used while LLMObs is disabled.") - return None + if cls.enabled is False: + log.warning(SPAN_START_WHILE_DISABLED_WARNING) return cls._instance._start_span("agent", name=name, session_id=session_id, ml_app=ml_app) @classmethod @@ -268,9 +282,8 @@ def workflow( :returns: The Span object representing the traced operation. """ - if cls.enabled is False or cls._instance is None: - log.warning("LLMObs.workflow() cannot be used while LLMObs is disabled.") - return None + if cls.enabled is False: + log.warning(SPAN_START_WHILE_DISABLED_WARNING) return cls._instance._start_span("workflow", name=name, session_id=session_id, ml_app=ml_app) @classmethod @@ -295,14 +308,14 @@ def embedding( :returns: The Span object representing the traced operation. """ - if cls.enabled is False or cls._instance is None: - log.warning("LLMObs.embedding() cannot be used while LLMObs is disabled.") - return None + if cls.enabled is False: + log.warning(SPAN_START_WHILE_DISABLED_WARNING) if not model_name: - log.warning("model_name must be the specified name of the invoked model.") - return None + log.warning("LLMObs.embedding() missing model_name") if model_provider is None: model_provider = "custom" + if model_name is None: + model_name = "unknown" return cls._instance._start_span( "embedding", name, @@ -326,9 +339,8 @@ def retrieval( :returns: The Span object representing the traced operation. """ - if cls.enabled is False or cls._instance is None: - log.warning("LLMObs.retrieval() cannot be used while LLMObs is disabled.") - return None + if cls.enabled is False: + log.warning(SPAN_START_WHILE_DISABLED_WARNING) return cls._instance._start_span("retrieval", name=name, session_id=session_id, ml_app=ml_app) @classmethod @@ -369,9 +381,6 @@ def annotate( :param metrics: Dictionary of JSON serializable key-value metric pairs, such as `{prompt,completion,total}_tokens`. """ - if cls.enabled is False or cls._instance is None: - log.warning("LLMObs.annotate() cannot be used while LLMObs is disabled.") - return if span is None: span = cls._instance.tracer.current_span() if span is None: @@ -562,8 +571,10 @@ def submit_evaluation( :param value: The value of the evaluation metric. Must be a string (categorical), integer (numerical/score), or float (numerical/score). """ - if cls.enabled is False or cls._instance is None or cls._instance._llmobs_eval_metric_writer is None: - log.warning("LLMObs.submit_evaluation() requires LLMObs to be enabled.") + if cls.enabled is False: + log.warning( + "LLMObs.submit_evaluation() called when LLMObs is not enabled. Evaluation metric data will not be sent." + ) return if not isinstance(span_context, dict): log.warning( @@ -597,3 +608,7 @@ def submit_evaluation( "{}_value".format(metric_type): value, } ) + + +# initialize the default llmobs instance +LLMObs._instance = LLMObs() diff --git a/ddtrace/llmobs/_utils.py b/ddtrace/llmobs/_utils.py index 8a4e7713562..f557efdb1dc 100644 --- a/ddtrace/llmobs/_utils.py +++ b/ddtrace/llmobs/_utils.py @@ -38,7 +38,7 @@ def _get_ml_app(span: Span) -> str: nearest_llmobs_ancestor = _get_nearest_llmobs_ancestor(span) if nearest_llmobs_ancestor: ml_app = nearest_llmobs_ancestor.get_tag(ML_APP) - return ml_app or config._llmobs_ml_app + return ml_app or config._llmobs_ml_app or "uknown-ml-app" def _get_session_id(span: Span) -> str: diff --git a/ddtrace/llmobs/decorators.py b/ddtrace/llmobs/decorators.py index cdb9dd9762e..b2356520132 100644 --- a/ddtrace/llmobs/decorators.py +++ b/ddtrace/llmobs/decorators.py @@ -4,6 +4,7 @@ from ddtrace.internal.logger import get_logger from ddtrace.llmobs import LLMObs +from ddtrace.llmobs._constants import SPAN_START_WHILE_DISABLED_WARNING log = get_logger(__name__) @@ -21,12 +22,13 @@ def decorator( def inner(func): @wraps(func) def wrapper(*args, **kwargs): - if not LLMObs.enabled or LLMObs._instance is None: - log.warning("LLMObs.%s() cannot be used while LLMObs is disabled.", operation_kind) + if not LLMObs.enabled: + log.warning(SPAN_START_WHILE_DISABLED_WARNING) return func(*args, **kwargs) traced_model_name = model_name if traced_model_name is None: - raise TypeError("model_name is required for LLMObs.{}()".format(operation_kind)) + log.warning("model_name missing for LLMObs.%s() - default to 'unknown'", operation_kind) + traced_model_name = "unknown" span_name = name if span_name is None: span_name = func.__name__ @@ -57,8 +59,8 @@ def decorator( def inner(func): @wraps(func) def wrapper(*args, **kwargs): - if not LLMObs.enabled or LLMObs._instance is None: - log.warning("LLMObs.%s() cannot be used while LLMObs is disabled.", operation_kind) + if not LLMObs.enabled: + log.warning(SPAN_START_WHILE_DISABLED_WARNING) return func(*args, **kwargs) span_name = name if span_name is None: diff --git a/tests/llmobs/test_llmobs_decorators.py b/tests/llmobs/test_llmobs_decorators.py index 31ecfbf37e1..b948e3ad932 100644 --- a/tests/llmobs/test_llmobs_decorators.py +++ b/tests/llmobs/test_llmobs_decorators.py @@ -1,6 +1,7 @@ import mock import pytest +from ddtrace.llmobs._constants import SPAN_START_WHILE_DISABLED_WARNING from ddtrace.llmobs.decorators import agent from ddtrace.llmobs.decorators import embedding from ddtrace.llmobs.decorators import llm @@ -29,7 +30,7 @@ def f(): LLMObs.disable() f() - mock_logs.warning.assert_called_with("LLMObs.%s() cannot be used while LLMObs is disabled.", decorator_name) + mock_logs.warning.assert_called_with(SPAN_START_WHILE_DISABLED_WARNING) mock_logs.reset_mock() @@ -48,7 +49,7 @@ def f(): LLMObs.disable() f() - mock_logs.warning.assert_called_with("LLMObs.%s() cannot be used while LLMObs is disabled.", decorator_name) + mock_logs.warning.assert_called_with(SPAN_START_WHILE_DISABLED_WARNING) mock_logs.reset_mock() diff --git a/tests/llmobs/test_llmobs_service.py b/tests/llmobs/test_llmobs_service.py index 4b9153de1d5..0dd7bfc8eda 100644 --- a/tests/llmobs/test_llmobs_service.py +++ b/tests/llmobs/test_llmobs_service.py @@ -17,6 +17,7 @@ from ddtrace.llmobs._constants import OUTPUT_VALUE from ddtrace.llmobs._constants import SESSION_ID from ddtrace.llmobs._constants import SPAN_KIND +from ddtrace.llmobs._constants import SPAN_START_WHILE_DISABLED_WARNING from ddtrace.llmobs._constants import TAGS from ddtrace.llmobs._llmobs import LLMObsTraceProcessor from tests.llmobs._utils import _expected_llmobs_eval_metric_event @@ -53,8 +54,9 @@ def test_service_disable(): dummy_tracer = DummyTracer() llmobs_service.enable(tracer=dummy_tracer) llmobs_service.disable() - assert llmobs_service._instance is None assert llmobs_service.enabled is False + assert llmobs_service._instance._llmobs_eval_metric_writer.status.value == "stopped" + assert llmobs_service._instance._llmobs_span_writer.status.value == "stopped" def test_service_enable_no_api_key(): @@ -62,9 +64,9 @@ def test_service_enable_no_api_key(): dummy_tracer = DummyTracer() with pytest.raises(ValueError): llmobs_service.enable(tracer=dummy_tracer) - llmobs_instance = llmobs_service._instance - assert llmobs_instance is None assert llmobs_service.enabled is False + assert llmobs_service._instance._llmobs_eval_metric_writer.status.value == "stopped" + assert llmobs_service._instance._llmobs_span_writer.status.value == "stopped" def test_service_enable_no_ml_app_specified(): @@ -72,9 +74,9 @@ def test_service_enable_no_ml_app_specified(): dummy_tracer = DummyTracer() with pytest.raises(ValueError): llmobs_service.enable(tracer=dummy_tracer) - llmobs_instance = llmobs_service._instance - assert llmobs_instance is None assert llmobs_service.enabled is False + assert llmobs_service._instance._llmobs_eval_metric_writer.status.value == "stopped" + assert llmobs_service._instance._llmobs_span_writer.status.value == "stopped" def test_service_enable_already_enabled(mock_logs): @@ -94,19 +96,19 @@ def test_service_enable_already_enabled(mock_logs): def test_start_span_while_disabled_logs_warning(LLMObs, mock_logs): LLMObs.disable() _ = LLMObs.llm(model_name="test_model", name="test_llm_call", model_provider="test_provider") - mock_logs.warning.assert_called_once_with("LLMObs.llm() cannot be used while LLMObs is disabled.") + mock_logs.warning.assert_called_once_with(SPAN_START_WHILE_DISABLED_WARNING) mock_logs.reset_mock() _ = LLMObs.tool(name="test_tool") - mock_logs.warning.assert_called_once_with("LLMObs.tool() cannot be used while LLMObs is disabled.") + mock_logs.warning.assert_called_once_with(SPAN_START_WHILE_DISABLED_WARNING) mock_logs.reset_mock() _ = LLMObs.task(name="test_task") - mock_logs.warning.assert_called_once_with("LLMObs.task() cannot be used while LLMObs is disabled.") + mock_logs.warning.assert_called_once_with(SPAN_START_WHILE_DISABLED_WARNING) mock_logs.reset_mock() _ = LLMObs.workflow(name="test_workflow") - mock_logs.warning.assert_called_once_with("LLMObs.workflow() cannot be used while LLMObs is disabled.") + mock_logs.warning.assert_called_once_with(SPAN_START_WHILE_DISABLED_WARNING) mock_logs.reset_mock() _ = LLMObs.agent(name="test_agent") - mock_logs.warning.assert_called_once_with("LLMObs.agent() cannot be used while LLMObs is disabled.") + mock_logs.warning.assert_called_once_with(SPAN_START_WHILE_DISABLED_WARNING) def test_start_span_uses_kind_as_default_name(LLMObs): @@ -167,7 +169,7 @@ def test_llm_span_no_model_raises_error(LLMObs, mock_logs): def test_llm_span_empty_model_name_logs_warning(LLMObs, mock_logs): _ = LLMObs.llm(model_name="", name="test_llm_call", model_provider="test_provider") - mock_logs.warning.assert_called_once_with("model_name must be the specified name of the invoked model.") + mock_logs.warning.assert_called_once_with("LLMObs.llm() missing model_name") def test_default_model_provider_set_to_custom(LLMObs): @@ -224,7 +226,7 @@ def test_embedding_span_no_model_raises_error(LLMObs): def test_embedding_span_empty_model_name_logs_warning(LLMObs, mock_logs): _ = LLMObs.embedding(model_name="", name="test_embedding", model_provider="test_provider") - mock_logs.warning.assert_called_once_with("model_name must be the specified name of the invoked model.") + mock_logs.warning.assert_called_once_with("LLMObs.embedding() missing model_name") def test_embedding_default_model_provider_set_to_custom(LLMObs): @@ -252,12 +254,6 @@ def test_embedding_span(LLMObs, mock_llmobs_span_writer): ) -def test_annotate_while_disabled_logs_warning(LLMObs, mock_logs): - LLMObs.disable() - LLMObs.annotate(parameters={"test": "test"}) - mock_logs.warning.assert_called_once_with("LLMObs.annotate() cannot be used while LLMObs is disabled.") - - def test_annotate_no_active_span_logs_warning(LLMObs, mock_logs): LLMObs.annotate(parameters={"test": "test"}) mock_logs.warning.assert_called_once_with("No span provided and no active LLMObs-generated span found.") @@ -668,12 +664,6 @@ def test_ml_app_override(LLMObs, mock_llmobs_span_writer): ) -def test_export_span_llmobs_not_enabled_raises_warning(LLMObs, mock_logs): - LLMObs.disable() - LLMObs.export_span() - mock_logs.warning.assert_called_once_with("LLMObs.export_span() requires LLMObs to be enabled.") - - def test_export_span_specified_span_is_incorrect_type_raises_warning(LLMObs, mock_logs): LLMObs.export_span(span="asd") mock_logs.warning.assert_called_once_with("Failed to export span. Span must be a valid Span object.") @@ -717,7 +707,9 @@ def test_submit_evaluation_llmobs_disabled_raises_warning(LLMObs, mock_logs): LLMObs.submit_evaluation( span_context={"span_id": "123", "trace_id": "456"}, label="toxicity", metric_type="categorical", value="high" ) - mock_logs.warning.assert_called_once_with("LLMObs.submit_evaluation() requires LLMObs to be enabled.") + mock_logs.warning.assert_called_once_with( + "LLMObs.submit_evaluation() called when LLMObs is not enabled. Evaluation metric data will not be sent." + ) def test_submit_evaluation_span_context_incorrect_type_raises_warning(LLMObs, mock_logs): From cee0c7b33a111cc74f1621472bc8a97e92c388e6 Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Tue, 14 May 2024 09:29:57 -0400 Subject: [PATCH 042/104] chore(ci): convert flask_sqli benchmark to use Flask test client instead of gunicorn (#9253) Follow up from #8902 to also convert the `flask_sqli` benchmark to using the Flask test client instead of gunicorn. This cuts the benchmark runtime from 25min to 5min. We should be testing the same code paths, but we avoid the need to spin up a subprocess/server and make network requests to it. We also refactored some common bits from both `flask_simple` and `flask_sqli` to ensure we are configuring them the same way. This will look like a performance improvement, but it isn't. It is the test itself getting faster. ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [ ] Title is accurate - [ ] All changes are related to the pull request's stated goal - [ ] Description motivates each change - [ ] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [ ] Testing strategy adequately addresses listed risks - [ ] Change is maintainable (easy to change, telemetry, documentation) - [ ] Release note makes sense to a user of the library - [ ] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [ ] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .gitlab/benchmarks.yml | 2 +- benchmarks/bm/flask_utils.py | 202 +++++++++++------- benchmarks/flask_simple/scenario.py | 122 +---------- benchmarks/flask_sqli/app.py | 23 -- benchmarks/flask_sqli/gunicorn.conf.py | 8 - .../flask_sqli/requirements_scenario.txt | 2 - benchmarks/flask_sqli/scenario.py | 43 ++-- benchmarks/flask_sqli/utils.py | 20 -- 8 files changed, 160 insertions(+), 262 deletions(-) delete mode 100644 benchmarks/flask_sqli/app.py delete mode 100644 benchmarks/flask_sqli/gunicorn.conf.py delete mode 100644 benchmarks/flask_sqli/utils.py diff --git a/.gitlab/benchmarks.yml b/.gitlab/benchmarks.yml index 17854287dda..8dc899c21d1 100644 --- a/.gitlab/benchmarks.yml +++ b/.gitlab/benchmarks.yml @@ -15,7 +15,7 @@ variables: - git config --global url."https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.ddbuild.io/DataDog/".insteadOf "https://github.com/DataDog/" - git clone --branch dd-trace-py https://github.com/DataDog/benchmarking-platform /platform && cd /platform - ./steps/capture-hardware-software-info.sh - - '([ $SCENARIO = "flask_simple" ] && BP_SCENARIO=$SCENARIO /benchmarking-platform-tools/bp-runner/bp-runner "$REPORTS_DIR/../.gitlab/benchmarks/bp-runner.yml" --debug -t) || ([ $SCENARIO != "flask_simple" ] && ./steps/run-benchmarks.sh)' + - '([[ $SCENARIO =~ ^flask_* ]] && BP_SCENARIO=$SCENARIO /benchmarking-platform-tools/bp-runner/bp-runner "$REPORTS_DIR/../.gitlab/benchmarks/bp-runner.yml" --debug -t) || (! [[ $SCENARIO =~ ^flask_* ]] && ./steps/run-benchmarks.sh)' - ./steps/analyze-results.sh - "./steps/upload-results-to-s3.sh || :" artifacts: diff --git a/benchmarks/bm/flask_utils.py b/benchmarks/bm/flask_utils.py index 2f427d1a84b..f297b07e26f 100644 --- a/benchmarks/bm/flask_utils.py +++ b/benchmarks/bm/flask_utils.py @@ -1,81 +1,135 @@ -from contextlib import contextmanager +import hashlib import os -import subprocess - -import requests -import tenacity - - -SERVER_URL = "http://0.0.0.0:8000/" - - -def _get_response(): - HEADERS = { - "User-Agent": "dd-test-scanner-log", - } - r = requests.get(SERVER_URL, headers=HEADERS) - r.raise_for_status() - - -@tenacity.retry( - wait=tenacity.wait_fixed(1), - stop=tenacity.stop_after_attempt(30), -) -def _wait(): - _get_response() - - -@contextmanager -def server(scenario, custom_post_response): - env = { - "PERF_TRACER_ENABLED": str(scenario.tracer_enabled), - "PERF_PROFILER_ENABLED": str(scenario.profiler_enabled), - "PERF_DEBUGGER_ENABLED": str(scenario.debugger_enabled), - "DD_APPSEC_ENABLED": str(scenario.appsec_enabled), - "DD_IAST_ENABLED": str(scenario.iast_enabled), - "DD_TELEMETRY_METRICS_ENABLED": str(scenario.telemetry_metrics_enabled), - } - # copy over current environ - env.update(os.environ) - cmd = ["gunicorn", "-c", "gunicorn.conf.py"] - proc = subprocess.Popen( - cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - close_fds=True, - env=env, +import random +import sqlite3 + +import attr +import bm +from flask import Flask +from flask import Response +from flask import render_template_string +from flask import request + +from ddtrace.debugging._probe.model import DEFAULT_CAPTURE_LIMITS +from ddtrace.debugging._probe.model import DEFAULT_SNAPSHOT_PROBE_RATE +from ddtrace.debugging._probe.model import LiteralTemplateSegment +from ddtrace.debugging._probe.model import LogLineProbe + + +def make_index(): + rand_numbers = [random.random() for _ in range(20)] + m = hashlib.md5() + m.update(b"Insecure hash") + rand_numbers.append(m.digest()) + return render_template_string( + """ + + + + + + Hello World! + + +
+
+

+ Hello World +

+

+ My first website +

+
    + {% for i in rand_numbers %} +
  • {{ i }}
  • + {% endfor %} +
+
+
+ + + """, + rand_numbers=rand_numbers, ) - # make sure process has been started - assert proc.poll() is None - try: - _wait() - if scenario.post_request: - response = custom_post_response - else: - response = _get_response - yield response - finally: - proc.terminate() - proc.wait() - - -def post_fork(server, worker): - # Set lower defaults for ensuring profiler collect is run - if os.environ.get("PERF_PROFILER_ENABLED") == "True": + + +def create_app(): + app = Flask(__name__) + + con = sqlite3.connect(":memory:", check_same_thread=False) + cur = con.cursor() + + @app.route("/") + def index(): + return make_index() + + @app.route("/post-view", methods=["POST"]) + def post_view(): + data = request.data + return data, 200 + + @app.route("/sqli", methods=["POST"]) + def sqli(): + sql = "SELECT 1 FROM sqlite_master WHERE name = '" + request.form["username"] + "'" + cur.execute(sql) + return Response("OK") + + return app + + +@attr.s() +class FlaskScenarioMixin: + tracer_enabled = bm.var_bool() + profiler_enabled = bm.var_bool() + debugger_enabled = bm.var_bool() + appsec_enabled = bm.var_bool() + iast_enabled = bm.var_bool() + post_request = bm.var_bool() + telemetry_metrics_enabled = bm.var_bool() + + def setup(self): + # Setup the environment and enable Datadog features os.environ.update( - {"DD_PROFILING_ENABLED": "1", "DD_PROFILING_API_TIMEOUT": "0.1", "DD_PROFILING_UPLOAD_INTERVAL": "10"} + { + "DD_APPSEC_ENABLED": str(self.appsec_enabled), + "DD_IAST_ENABLED": str(self.iast_enabled), + "DD_TELEMETRY_METRICS_ENABLED": str(self.telemetry_metrics_enabled), + } ) - # This will not work with gevent workers as the gevent hub has not been - # initialized when this hook is called. - if os.environ.get("PERF_TRACER_ENABLED") == "True": - import ddtrace.bootstrap.sitecustomize # noqa:F401 + if self.profiler_enabled: + os.environ.update( + {"DD_PROFILING_ENABLED": "1", "DD_PROFILING_API_TIMEOUT": "0.1", "DD_PROFILING_UPLOAD_INTERVAL": "10"} + ) + if not self.tracer_enabled: + import ddtrace.profiling.auto # noqa:F401 + + if self.tracer_enabled: + import ddtrace.bootstrap.sitecustomize # noqa:F401 + + if self.debugger_enabled: + from bm.di_utils import BMDebugger + BMDebugger.enable() -def post_worker_init(worker): - # If profiling enabled but not tracer than only run auto script for profiler - if os.environ.get("PERF_PROFILER_ENABLED") == "1" and os.environ.get("PERF_TRACER_ENABLED") == "0": - import ddtrace.profiling.auto # noqa:F401 - if os.environ.get("PERF_DEBUGGER_ENABLED") == "True": - from bm.di_utils import BMDebugger + # Probes are added only if the BMDebugger is enabled. + probe_id = "bm-test" + BMDebugger.add_probes( + LogLineProbe( + probe_id=probe_id, + version=0, + tags={}, + source_file="scenario.py", + line=23, + template=probe_id, + segments=[LiteralTemplateSegment(probe_id)], + take_snapshot=True, + limits=DEFAULT_CAPTURE_LIMITS, + condition=None, + condition_error_rate=0.0, + rate=DEFAULT_SNAPSHOT_PROBE_RATE, + ), + ) - BMDebugger.enable() + def create_app(self): + self.setup() + return create_app() diff --git a/benchmarks/flask_simple/scenario.py b/benchmarks/flask_simple/scenario.py index 311661d6a7b..8027b990df8 100644 --- a/benchmarks/flask_simple/scenario.py +++ b/benchmarks/flask_simple/scenario.py @@ -1,125 +1,11 @@ -import hashlib -import os -import random - import bm -import bm.utils as utils -from flask import Flask -from flask import render_template_string -from flask import request - -from ddtrace.debugging._probe.model import DEFAULT_CAPTURE_LIMITS -from ddtrace.debugging._probe.model import DEFAULT_SNAPSHOT_PROBE_RATE -from ddtrace.debugging._probe.model import LiteralTemplateSegment -from ddtrace.debugging._probe.model import LogLineProbe - - -def make_index(): - rand_numbers = [random.random() for _ in range(20)] - m = hashlib.md5() - m.update(b"Insecure hash") - rand_numbers.append(m.digest()) - return render_template_string( - """ - - - - - - Hello World! - - -
-
-

- Hello World -

-

- My first website -

-
    - {% for i in rand_numbers %} -
  • {{ i }}
  • - {% endfor %} -
-
-
- - - """, - rand_numbers=rand_numbers, - ) - - -def create_app(): - app = Flask(__name__) - - @app.route("/") - def index(): - return make_index() - - @app.route("/post-view", methods=["POST"]) - def post_view(): - data = request.data - return data, 200 +from bm import utils +from bm.flask_utils import FlaskScenarioMixin - return app - - -class FlaskSimple(bm.Scenario): - tracer_enabled = bm.var_bool() - profiler_enabled = bm.var_bool() - debugger_enabled = bm.var_bool() - appsec_enabled = bm.var_bool() - iast_enabled = bm.var_bool() - post_request = bm.var_bool() - telemetry_metrics_enabled = bm.var_bool() +class FlaskSimple(FlaskScenarioMixin, bm.Scenario): def run(self): - # Setup the environment and enable Datadog features - os.environ.update( - { - "DD_APPSEC_ENABLED": str(self.appsec_enabled), - "DD_IAST_ENABLED": str(self.iast_enabled), - "DD_TELEMETRY_METRICS_ENABLED": str(self.telemetry_metrics_enabled), - } - ) - if self.profiler_enabled: - os.environ.update( - {"DD_PROFILING_ENABLED": "1", "DD_PROFILING_API_TIMEOUT": "0.1", "DD_PROFILING_UPLOAD_INTERVAL": "10"} - ) - if not self.tracer_enabled: - import ddtrace.profiling.auto # noqa:F401 - - if self.tracer_enabled: - import ddtrace.bootstrap.sitecustomize # noqa:F401 - - if self.debugger_enabled: - from bm.di_utils import BMDebugger - - BMDebugger.enable() - - # Probes are added only if the BMDebugger is enabled. - probe_id = "bm-test" - BMDebugger.add_probes( - LogLineProbe( - probe_id=probe_id, - version=0, - tags={}, - source_file="scenario.py", - line=23, - template=probe_id, - segments=[LiteralTemplateSegment(probe_id)], - take_snapshot=True, - limits=DEFAULT_CAPTURE_LIMITS, - condition=None, - condition_error_rate=0.0, - rate=DEFAULT_SNAPSHOT_PROBE_RATE, - ), - ) - - # Create the Flask app - app = create_app() + app = self.create_app() # Setup the request function if self.post_request: diff --git a/benchmarks/flask_sqli/app.py b/benchmarks/flask_sqli/app.py deleted file mode 100644 index 543fea8b1c5..00000000000 --- a/benchmarks/flask_sqli/app.py +++ /dev/null @@ -1,23 +0,0 @@ -import sqlite3 - -from flask import Flask -from flask import Response -from flask import request - - -app = Flask(__name__) - -con = sqlite3.connect(":memory:", check_same_thread=False) -cur = con.cursor() - - -@app.route("/", methods=["GET"]) -def index(): - return Response("OK") - - -@app.route("/sqli", methods=["POST"]) -def sqli(): - sql = "SELECT 1 FROM sqlite_master WHERE name = '" + request.form["username"] + "'" - cur.execute(sql) - return Response("OK") diff --git a/benchmarks/flask_sqli/gunicorn.conf.py b/benchmarks/flask_sqli/gunicorn.conf.py deleted file mode 100644 index 1b2d62879b4..00000000000 --- a/benchmarks/flask_sqli/gunicorn.conf.py +++ /dev/null @@ -1,8 +0,0 @@ -from bm.flask_utils import post_fork # noqa:I001,F401 -from bm.flask_utils import post_worker_init # noqa:F401 - -bind = "0.0.0.0:8000" -worker_class = "sync" -workers = 1 -wsgi_app = "app:app" -pidfile = "gunicorn.pid" diff --git a/benchmarks/flask_sqli/requirements_scenario.txt b/benchmarks/flask_sqli/requirements_scenario.txt index ee57bcb69b0..5bd19d39d1a 100644 --- a/benchmarks/flask_sqli/requirements_scenario.txt +++ b/benchmarks/flask_sqli/requirements_scenario.txt @@ -1,3 +1 @@ flask==3.0.0 -gunicorn==20.1.0 -requests==2.31.0 diff --git a/benchmarks/flask_sqli/scenario.py b/benchmarks/flask_sqli/scenario.py index 2fca7625872..1bd37eabb0e 100644 --- a/benchmarks/flask_sqli/scenario.py +++ b/benchmarks/flask_sqli/scenario.py @@ -1,22 +1,33 @@ import bm -import bm.flask_utils as flask_utils -from utils import _post_response +from bm.flask_utils import FlaskScenarioMixin -class FlaskSQLi(bm.Scenario): - tracer_enabled = bm.var_bool() - profiler_enabled = bm.var_bool() - debugger_enabled = bm.var_bool() - appsec_enabled = bm.var_bool() - iast_enabled = bm.var_bool() - post_request = bm.var_bool() - telemetry_metrics_enabled = bm.var_bool() - +class FlaskSQLi(FlaskScenarioMixin, bm.Scenario): def run(self): - with flask_utils.server(self, custom_post_response=_post_response) as get_response: + app = self.create_app() + + # Setup the request function + headers = { + "SERVER_PORT": "8000", + "REMOTE_ADDR": "127.0.0.1", + "HTTP_HOST": "localhost:8000", + "HTTP_ACCEPT": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp," + "image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", + "HTTP_SEC_FETCH_DEST": "document", + "HTTP_ACCEPT_ENCODING": "gzip, deflate, br", + "HTTP_ACCEPT_LANGUAGE": "en-US,en;q=0.9", + "User-Agent": "dd-test-scanner-log", + } + + def make_request(app): + client = app.test_client() + return client.post("/sqli", headers=headers, data={"username": "shaquille_oatmeal", "password": "123456"}) - def _(loops): - for _ in range(500): - get_response() + # Scenario loop function + def _(loops): + for _ in range(loops): + res = make_request(app) + assert res.status_code == 200 + res.close() - yield _ + yield _ diff --git a/benchmarks/flask_sqli/utils.py b/benchmarks/flask_sqli/utils.py deleted file mode 100644 index 07df315c7a0..00000000000 --- a/benchmarks/flask_sqli/utils.py +++ /dev/null @@ -1,20 +0,0 @@ -import bm.flask_utils as flask_utils -import requests - - -def _post_response(): - HEADERS = { - "SERVER_PORT": "8000", - "REMOTE_ADDR": "127.0.0.1", - "HTTP_HOST": "localhost:8000", - "HTTP_ACCEPT": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp," - "image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", - "HTTP_SEC_FETCH_DEST": "document", - "HTTP_ACCEPT_ENCODING": "gzip, deflate, br", - "HTTP_ACCEPT_LANGUAGE": "en-US,en;q=0.9", - "User-Agent": "dd-test-scanner-log", - } - r = requests.post( - flask_utils.SERVER_URL + "sqli", data={"username": "shaquille_oatmeal", "password": "123456"}, headers=HEADERS - ) - r.raise_for_status() From 2c1420e01fe3c1fb130055e6cb9ff93bed67771d Mon Sep 17 00:00:00 2001 From: lievan <42917263+lievan@users.noreply.github.com> Date: Tue, 14 May 2024 10:22:08 -0400 Subject: [PATCH 043/104] feat(llmobs): expose force flush method for serverless (#9243) This PR exposes a blocking flush method to allow people to force flush any buffered LLMObs data. A short term fix for LLMObs data not sending in short-lived serverless apps since the app dies before all LLMObs data is sent. ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: lievan --- ddtrace/llmobs/_llmobs.py | 14 ++++++++++++++ tests/llmobs/test_llmobs_service.py | 19 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/ddtrace/llmobs/_llmobs.py b/ddtrace/llmobs/_llmobs.py index 75393e461ae..8cd59bdc4af 100644 --- a/ddtrace/llmobs/_llmobs.py +++ b/ddtrace/llmobs/_llmobs.py @@ -132,6 +132,20 @@ def disable(cls) -> None: log.debug("%s disabled", cls.__name__) + @classmethod + def flush(cls): + """ + Flushes any remaining spans and evaluation metrics to the LLMObs backend. + """ + if cls.enabled is False: + log.warning("flushing when LLMObs is disabled. No spans or evaluation metrics will be sent.") + return + try: + cls._instance._llmobs_span_writer.periodic() + cls._instance._llmobs_eval_metric_writer.periodic() + except Exception: + log.warning("Failed to flush LLMObs spans and evaluation metrics.", exc_info=True) + @classmethod def export_span(cls, span: Optional[Span] = None) -> Optional[ExportedLLMObsSpan]: """Returns a simple representation of a span to export its span and trace IDs. diff --git a/tests/llmobs/test_llmobs_service.py b/tests/llmobs/test_llmobs_service.py index 0dd7bfc8eda..74717cdf47a 100644 --- a/tests/llmobs/test_llmobs_service.py +++ b/tests/llmobs/test_llmobs_service.py @@ -840,3 +840,22 @@ def test_submit_evaluation_enqueues_writer_with_numerical_metric(LLMObs, mock_ll numerical_value=35, ) ) + + +def test_flush_calls_periodic(LLMObs, mock_llmobs_span_writer, mock_llmobs_eval_metric_writer): + LLMObs.flush() + mock_llmobs_span_writer.periodic.assert_called_once() + mock_llmobs_eval_metric_writer.periodic.assert_called_once() + + +def test_flush_does_not_call_period_when_llmobs_is_disabled( + LLMObs, mock_llmobs_span_writer, mock_llmobs_eval_metric_writer, mock_logs +): + LLMObs.disable() + LLMObs.flush() + mock_llmobs_span_writer.periodic.assert_not_called() + mock_llmobs_eval_metric_writer.periodic.assert_not_called() + mock_logs.warning.assert_has_calls( + [mock.call("flushing when LLMObs is disabled. No spans or evaluation metrics will be sent.")] + ) + LLMObs.enable() From e05643340ac2558aa0ff269e407f0796d59fc0e5 Mon Sep 17 00:00:00 2001 From: Juanjo Alvarez Martinez Date: Tue, 14 May 2024 17:17:06 +0200 Subject: [PATCH 044/104] fix(asm): fix format value using `str` instead of `format` to return a value without `format_spec` (#9256) ## Description The `format_value` aspect was using `str()` to convert the value to string when there was no `format_spec`. This produced different strings on Python <=3.10 when formatting an `Enum` member. The correct way instead is to call `format` without spec. ## Checklist - [X] Change(s) are motivated and described in the PR description - [X] Testing strategy is described if automated tests are not included in the PR - [X] Risks are described (performance impact, potential for breakage, maintainability) - [X] Change is maintainable (easy to change, telemetry, documentation) - [X] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [X] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [X] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [X] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Signed-off-by: Juanjo Alvarez Co-authored-by: Alberto Vara --- .../appsec/_iast/_taint_tracking/aspects.py | 2 +- .../aspects/test_format_aspect_fixtures.py | 33 +++++++++++++ .../iast/fixtures/aspects/str_methods.py | 46 +++++++++++++++++++ 3 files changed, 80 insertions(+), 1 deletion(-) diff --git a/ddtrace/appsec/_iast/_taint_tracking/aspects.py b/ddtrace/appsec/_iast/_taint_tracking/aspects.py index 843f324dcae..a2a8595a2d2 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/aspects.py +++ b/ddtrace/appsec/_iast/_taint_tracking/aspects.py @@ -534,7 +534,7 @@ def format_value_aspect( else: return ("{:%s}" % format_spec).format(new_text) else: - return str_aspect(str, 0, new_text) + return format(new_text) except Exception as e: iast_taint_log_error("IAST propagation error. format_value_aspect. {}".format(e)) return new_text diff --git a/tests/appsec/iast/aspects/test_format_aspect_fixtures.py b/tests/appsec/iast/aspects/test_format_aspect_fixtures.py index b2782827f0f..e280a872754 100644 --- a/tests/appsec/iast/aspects/test_format_aspect_fixtures.py +++ b/tests/appsec/iast/aspects/test_format_aspect_fixtures.py @@ -1,4 +1,5 @@ # -*- encoding: utf-8 -*- +from enum import Enum import logging import math from typing import Any @@ -252,3 +253,35 @@ def test_propagate_ranges_with_no_context(caplog): log_messages = [record.message for record in caplog.get_records("call")] assert not any("[IAST] " in message for message in log_messages), log_messages assert len(ranges_result) == 0 + + +class ExportType(str, Enum): + USAGE = "Usage" + ACTUAL_COST = "ActualCost" + + +def test_format_value_aspect_no_change_patched_unpatched(): + # Issue: https://datadoghq.atlassian.net/jira/software/c/projects/APPSEC/boards/1141?selectedIssue=APPSEC-53155 + fstr_unpatched = f"{ExportType.ACTUAL_COST}" + fstr_patched = mod.do_exporttype_member_format() + assert fstr_patched == fstr_unpatched + + +class CustomSpec: + def __str__(self): + return "str" + + def __repr__(self): + return "repr" + + def __format__(self, format_spec): + return "format_" + format_spec + + +def test_format_value_aspect_no_change_customspec(): + c = CustomSpec() + assert f"{c}" == mod.do_customspec_simple() + assert f"{c!s}" == mod.do_customspec_cstr() + assert f"{c!r}" == mod.do_customspec_repr() + assert f"{c!a}" == mod.do_customspec_ascii() + assert f"{c!s:<20s}" == mod.do_customspec_formatspec() diff --git a/tests/appsec/iast/fixtures/aspects/str_methods.py b/tests/appsec/iast/fixtures/aspects/str_methods.py index dd748b732c4..a97d4a1c878 100644 --- a/tests/appsec/iast/fixtures/aspects/str_methods.py +++ b/tests/appsec/iast/fixtures/aspects/str_methods.py @@ -1,4 +1,5 @@ from collections import namedtuple +from enum import Enum import functools from http.client import HTTPConnection from http.server import HTTPServer as HTTPServer @@ -1154,3 +1155,48 @@ def do_stringio_init_param(StringIO, string_input): def do_stringio_init_and_getvalue_param(StringIO, string_input): xxx = StringIO(string_input) return xxx.getvalue() + + +class ExportType(str, Enum): + USAGE = "Usage" + ACTUAL_COST = "ActualCost" + + +def do_exporttype_member_format(): + return f"{ExportType.ACTUAL_COST}" + + +class CustomSpec: + def __str__(self): + return "str" + + def __repr__(self): + return "repr" + + def __format__(self, format_spec): + return "format_" + format_spec + + +def do_customspec_simple(): + c = CustomSpec() + return f"{c}" + + +def do_customspec_cstr(): + c = CustomSpec() + return f"{c!s}" + + +def do_customspec_repr(): + c = CustomSpec() + return f"{c!r}" + + +def do_customspec_ascii(): + c = CustomSpec() + return f"{c!a}" + + +def do_customspec_formatspec(): + c = CustomSpec() + return f"{c!s:<20s}" From a6376e7dcc83d3da4de1f867de6c525bb204bca6 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Tue, 14 May 2024 17:59:00 +0200 Subject: [PATCH 045/104] feat(iast): ensure no side-effects in IAST aspects (#9257) Ensure IAST propagations do not raise side effects related to Magic methods. This PR continues this #9244 ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. - [x] If change touches code that signs or publishes builds or packages, or handles credentials of any kind, I've requested a review from `@DataDog/security-design-and-guidance`. ## Reviewer Checklist - [ ] Title is accurate - [ ] All changes are related to the pull request's stated goal - [ ] Description motivates each change - [ ] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [ ] Testing strategy adequately addresses listed risks - [ ] Change is maintainable (easy to change, telemetry, documentation) - [ ] Release note makes sense to a user of the library - [ ] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [ ] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../appsec/_iast/_taint_tracking/__init__.py | 16 +- .../appsec/_iast/_taint_tracking/aspects.py | 44 +- ...ast-fix-side-effects-aad844523a335cae.yaml | 4 + .../iast/aspects/test_add_aspect_fixtures.py | 17 +- .../appsec/iast/aspects/test_side_effects.py | 446 +++++++++++++++++- .../iast/fixtures/aspects/str_methods.py | 30 +- tests/appsec/iast/iast_utils_side_effects.py | 57 ++- 7 files changed, 554 insertions(+), 60 deletions(-) create mode 100644 releasenotes/notes/iast-fix-side-effects-aad844523a335cae.yaml diff --git a/ddtrace/appsec/_iast/_taint_tracking/__init__.py b/ddtrace/appsec/_iast/_taint_tracking/__init__.py index afada1d72d2..ad31573add6 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/__init__.py +++ b/ddtrace/appsec/_iast/_taint_tracking/__init__.py @@ -135,7 +135,11 @@ def is_pyobject_tainted(pyobject: Any) -> bool: def taint_pyobject(pyobject: Any, source_name: Any, source_value: Any, source_origin=None) -> Any: # Pyobject must be Text with len > 1 - if not pyobject or not isinstance(pyobject, IAST.TEXT_TYPES): + if not isinstance(pyobject, IAST.TEXT_TYPES): + return pyobject + # We need this validation in different contition if pyobject is not a text type and creates a side-effect such as + # __len__ magic method call. + if len(pyobject) == 0: return pyobject if isinstance(source_name, (bytes, bytearray)): @@ -157,17 +161,19 @@ def taint_pyobject(pyobject: Any, source_name: Any, source_value: Any, source_or return pyobject -def taint_pyobject_with_ranges(pyobject: Any, ranges: Tuple) -> None: - if not pyobject or not isinstance(pyobject, IAST.TEXT_TYPES): - return None +def taint_pyobject_with_ranges(pyobject: Any, ranges: Tuple) -> bool: + if not isinstance(pyobject, IAST.TEXT_TYPES): + return False try: set_ranges(pyobject, ranges) + return True except ValueError as e: iast_taint_log_error("Tainting object with ranges error (pyobject type %s): %s" % (type(pyobject), e)) + return False def get_tainted_ranges(pyobject: Any) -> Tuple: - if not pyobject or not isinstance(pyobject, IAST.TEXT_TYPES): + if not isinstance(pyobject, IAST.TEXT_TYPES): return tuple() try: return get_ranges(pyobject) diff --git a/ddtrace/appsec/_iast/_taint_tracking/aspects.py b/ddtrace/appsec/_iast/_taint_tracking/aspects.py index a2a8595a2d2..f4d2d6d63d7 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/aspects.py +++ b/ddtrace/appsec/_iast/_taint_tracking/aspects.py @@ -88,7 +88,7 @@ def add_aspect(op1, op2): def split_aspect(orig_function: Optional[Callable], flag_added_args: int, *args: Any, **kwargs: Any) -> str: - if orig_function: + if orig_function is not None: if orig_function != builtin_str: if flag_added_args > 0: args = args[flag_added_args:] @@ -101,7 +101,7 @@ def split_aspect(orig_function: Optional[Callable], flag_added_args: int, *args: def rsplit_aspect(orig_function: Optional[Callable], flag_added_args: int, *args: Any, **kwargs: Any) -> str: - if orig_function: + if orig_function is not None: if orig_function != builtin_str: if flag_added_args > 0: args = args[flag_added_args:] @@ -127,7 +127,7 @@ def splitlines_aspect(orig_function: Optional[Callable], flag_added_args: int, * def str_aspect(orig_function: Optional[Callable], flag_added_args: int, *args: Any, **kwargs: Any) -> str: - if orig_function: + if orig_function is not None: if orig_function != builtin_str: if flag_added_args > 0: args = args[flag_added_args:] @@ -152,7 +152,7 @@ def str_aspect(orig_function: Optional[Callable], flag_added_args: int, *args: A def bytes_aspect(orig_function: Optional[Callable], flag_added_args: int, *args: Any, **kwargs: Any) -> bytes: - if orig_function: + if orig_function is not None: if orig_function != builtin_bytes: if flag_added_args > 0: args = args[flag_added_args:] @@ -170,7 +170,7 @@ def bytes_aspect(orig_function: Optional[Callable], flag_added_args: int, *args: def bytearray_aspect(orig_function: Optional[Callable], flag_added_args: int, *args: Any, **kwargs: Any) -> bytearray: - if orig_function: + if orig_function is not None: if orig_function != builtin_bytearray: if flag_added_args > 0: args = args[flag_added_args:] @@ -242,7 +242,7 @@ def slice_aspect(candidate_text: Text, start: int, stop: int, step: int) -> Text def bytearray_extend_aspect(orig_function: Optional[Callable], flag_added_args: int, *args: Any, **kwargs: Any) -> Any: - if orig_function and not isinstance(orig_function, BuiltinFunctionType): + if orig_function is not None and not isinstance(orig_function, BuiltinFunctionType): if flag_added_args > 0: args = args[flag_added_args:] return orig_function(*args, **kwargs) @@ -347,7 +347,7 @@ def ljust_aspect( def zfill_aspect( orig_function: Optional[Callable], flag_added_args: int, *args: Any, **kwargs: Any ) -> Union[TEXT_TYPES]: - if orig_function and not isinstance(orig_function, BuiltinFunctionType): + if orig_function is not None and not isinstance(orig_function, BuiltinFunctionType): if flag_added_args > 0: args = args[flag_added_args:] return orig_function(*args, **kwargs) @@ -425,12 +425,12 @@ def format_aspect( def format_map_aspect( orig_function: Optional[Callable], flag_added_args: int, *args: Any, **kwargs: Any ) -> Union[TEXT_TYPES]: - if orig_function and not isinstance(orig_function, BuiltinFunctionType): + if orig_function is not None and not isinstance(orig_function, BuiltinFunctionType): if flag_added_args > 0: args = args[flag_added_args:] return orig_function(*args, **kwargs) - if orig_function and not args: + if orig_function is not None and not args: return orig_function(*args, **kwargs) candidate_text: Text = args[0] @@ -596,7 +596,7 @@ def incremental_translation(self, incr_coder, funcode, empty): def decode_aspect( orig_function: Optional[Callable], flag_added_args: int, *args: Any, **kwargs: Any ) -> Union[TEXT_TYPES]: - if orig_function and (not flag_added_args or not args): + if orig_function is not None and (not flag_added_args or not args): # This patch is unexpected, so we fallback # to executing the original function return orig_function(*args, **kwargs) @@ -621,7 +621,7 @@ def decode_aspect( def encode_aspect( orig_function: Optional[Callable], flag_added_args: int, *args: Any, **kwargs: Any ) -> Union[TEXT_TYPES]: - if orig_function and (not flag_added_args or not args): + if orig_function is not None and (not flag_added_args or not args): # This patch is unexpected, so we fallback # to executing the original function return orig_function(*args, **kwargs) @@ -646,7 +646,7 @@ def encode_aspect( def upper_aspect( orig_function: Optional[Callable], flag_added_args: int, *args: Any, **kwargs: Any ) -> Union[TEXT_TYPES]: - if orig_function and (not isinstance(orig_function, BuiltinFunctionType) or not args): + if orig_function is not None and (not isinstance(orig_function, BuiltinFunctionType) or not args): if flag_added_args > 0: args = args[flag_added_args:] return orig_function(*args, **kwargs) @@ -666,7 +666,7 @@ def upper_aspect( def lower_aspect( orig_function: Optional[Callable], flag_added_args: int, *args: Any, **kwargs: Any ) -> Union[TEXT_TYPES]: - if orig_function and (not isinstance(orig_function, BuiltinFunctionType) or not args): + if orig_function is not None and (not isinstance(orig_function, BuiltinFunctionType) or not args): if flag_added_args > 0: args = args[flag_added_args:] return orig_function(*args, **kwargs) @@ -858,7 +858,7 @@ def aspect_replace_api( def replace_aspect( orig_function: Optional[Callable], flag_added_args: int, *args: Any, **kwargs: Any ) -> Union[TEXT_TYPES]: - if orig_function and (not isinstance(orig_function, BuiltinFunctionType) or not args): + if orig_function is not None and (not isinstance(orig_function, BuiltinFunctionType) or not args): if flag_added_args > 0: args = args[flag_added_args:] return orig_function(*args, **kwargs) @@ -905,7 +905,7 @@ def replace_aspect( def swapcase_aspect( orig_function: Optional[Callable], flag_added_args: int, *args: Any, **kwargs: Any ) -> Union[TEXT_TYPES]: - if orig_function and (not isinstance(orig_function, BuiltinFunctionType) or not args): + if orig_function is not None and (not isinstance(orig_function, BuiltinFunctionType) or not args): if flag_added_args > 0: args = args[flag_added_args:] return orig_function(*args, **kwargs) @@ -924,7 +924,7 @@ def swapcase_aspect( def title_aspect( orig_function: Optional[Callable], flag_added_args: int, *args: Any, **kwargs: Any ) -> Union[TEXT_TYPES]: - if orig_function and (not isinstance(orig_function, BuiltinFunctionType) or not args): + if orig_function is not None and (not isinstance(orig_function, BuiltinFunctionType) or not args): if flag_added_args > 0: args = args[flag_added_args:] return orig_function(*args, **kwargs) @@ -943,7 +943,7 @@ def title_aspect( def capitalize_aspect( orig_function: Optional[Callable], flag_added_args: int, *args: Any, **kwargs: Any ) -> Union[TEXT_TYPES]: - if orig_function and (not isinstance(orig_function, BuiltinFunctionType) or not args): + if orig_function is not None and (not isinstance(orig_function, BuiltinFunctionType) or not args): if flag_added_args > 0: args = args[flag_added_args:] return orig_function(*args, **kwargs) @@ -963,7 +963,7 @@ def capitalize_aspect( def casefold_aspect( orig_function: Optional[Callable], flag_added_args: int, *args: Any, **kwargs: Any ) -> Union[TEXT_TYPES]: - if orig_function: + if orig_function is not None: if not isinstance(orig_function, BuiltinFunctionType) or not args: if flag_added_args > 0: args = args[flag_added_args:] @@ -971,7 +971,11 @@ def casefold_aspect( else: orig_function = getattr(args[0], "casefold", None) - if orig_function and orig_function.__qualname__ not in ("str.casefold", "bytes.casefold", "bytearray.casefold"): + if orig_function is not None and getattr(orig_function, "__qualname__") not in ( + "str.casefold", + "bytes.casefold", + "bytearray.casefold", + ): if flag_added_args > 0: args = args[flag_added_args:] return orig_function(*args, **kwargs) @@ -992,7 +996,7 @@ def casefold_aspect( def translate_aspect( orig_function: Optional[Callable], flag_added_args: int, *args: Any, **kwargs: Any ) -> Union[TEXT_TYPES]: - if orig_function and (not isinstance(orig_function, BuiltinFunctionType) or not args): + if orig_function is not None and (not isinstance(orig_function, BuiltinFunctionType) or not args): if flag_added_args > 0: args = args[flag_added_args:] return orig_function(*args, **kwargs) diff --git a/releasenotes/notes/iast-fix-side-effects-aad844523a335cae.yaml b/releasenotes/notes/iast-fix-side-effects-aad844523a335cae.yaml new file mode 100644 index 00000000000..f1489535197 --- /dev/null +++ b/releasenotes/notes/iast-fix-side-effects-aad844523a335cae.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + Code Security: Ensure IAST propagation does not raise side effects related to Magic methods. diff --git a/tests/appsec/iast/aspects/test_add_aspect_fixtures.py b/tests/appsec/iast/aspects/test_add_aspect_fixtures.py index ed4fe4b278b..61a1ee5b2b4 100644 --- a/tests/appsec/iast/aspects/test_add_aspect_fixtures.py +++ b/tests/appsec/iast/aspects/test_add_aspect_fixtures.py @@ -78,7 +78,6 @@ def test_decoration_when_function_and_decorator_modify_texts_then_tainted( result = mod.do_add_and_uppercase(prefix, suffix) assert result == "AB" - # TODO: migrate aspect title assert len(get_tainted_ranges(result)) == 2 def test_string_operator_add_one_tainted_mixed_bytearray_bytes(self): # type: () -> None @@ -89,13 +88,7 @@ def test_string_operator_add_one_tainted_mixed_bytearray_bytes(self): # type: ( bar = bytearray("bar", encoding="utf-8") result = mod.do_operator_add_params(string_input, bar) assert result == b"foobar" - # TODO: error - # def add_aspect(op1, op2): - # if not isinstance(op1, TEXT_TYPES) or not isinstance(op2, TEXT_TYPES): - # return op1 + op2 - # > return _add_aspect(op1, op2) - # E SystemError: returned a result with an exception set - # assert len(get_tainted_ranges(result)) == 2 + assert len(get_tainted_ranges(result)) == 0 def test_string_operator_add_two_mixed_bytearray_bytes(self): # type: () -> None string_input = taint_pyobject( @@ -105,10 +98,4 @@ def test_string_operator_add_two_mixed_bytearray_bytes(self): # type: () -> Non result = mod.do_operator_add_params(string_input, bar) assert result == bytearray(b"foobar") - # TODO: error - # def add_aspect(op1, op2): - # if not isinstance(op1, TEXT_TYPES) or not isinstance(op2, TEXT_TYPES): - # return op1 + op2 - # > return _add_aspect(op1, op2) - # E SystemError: returned a result with an exception set - # assert len(get_tainted_ranges(result)) == 2 + assert len(get_tainted_ranges(result)) == 0 diff --git a/tests/appsec/iast/aspects/test_side_effects.py b/tests/appsec/iast/aspects/test_side_effects.py index c8a8f833043..4e39fe419a8 100644 --- a/tests/appsec/iast/aspects/test_side_effects.py +++ b/tests/appsec/iast/aspects/test_side_effects.py @@ -1,5 +1,11 @@ +import sys + import pytest +from ddtrace.appsec._iast._taint_tracking import OriginType +from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking import taint_pyobject +from ddtrace.appsec._iast._taint_tracking import taint_pyobject_with_ranges import ddtrace.appsec._iast._taint_tracking.aspects as ddtrace_aspects from tests.appsec.iast.aspects.conftest import _iast_patched_module from tests.appsec.iast.iast_utils_side_effects import MagicMethodsException @@ -7,20 +13,19 @@ mod = _iast_patched_module("tests.appsec.iast.fixtures.aspects.str_methods") +STRING_TO_TAINT = "abc" + def test_radd_aspect_side_effects(): - string_to_taint = "abc" - object_with_side_effects = MagicMethodsException(string_to_taint) + object_with_side_effects = MagicMethodsException(STRING_TO_TAINT) def __radd__(self, a): - print(self) - print(a) return self._data + a _old_method = getattr(MagicMethodsException, "__radd__", None) setattr(MagicMethodsException, "__radd__", __radd__) result = "123" + object_with_side_effects - assert result == string_to_taint + "123" + assert result == STRING_TO_TAINT + "123" result_tainted = ddtrace_aspects.add_aspect("123", object_with_side_effects) @@ -30,8 +35,7 @@ def __radd__(self, a): def test_add_aspect_side_effects(): - string_to_taint = "abc" - object_with_side_effects = MagicMethodsException(string_to_taint) + object_with_side_effects = MagicMethodsException(STRING_TO_TAINT) def __add__(self, a): return self._data + a @@ -48,9 +52,128 @@ def __add__(self, a): setattr(MagicMethodsException, "__radd__", _old_method) +def test_split_side_effects(): + object_with_side_effects = MagicMethodsException(STRING_TO_TAINT) + + result = mod.do_split_no_args(object_with_side_effects) + assert result == ["abc"] + + +def test_split_side_effects_none(): + with pytest.raises(AttributeError, match="'NoneType' object has no attribute 'split'"): + mod.do_split_no_args(None) + + +def test_rsplit_side_effects(): + def __call__(self): + return self._data + + _old_method = getattr(MagicMethodsException, "__call__", None) + setattr(MagicMethodsException, "__call__", __call__) + + object_with_side_effects = MagicMethodsException(STRING_TO_TAINT) + object_with_side_effects.rsplit = MagicMethodsException(STRING_TO_TAINT) + + result = object_with_side_effects.rsplit() + assert result + assert mod.do_rsplit_no_args(object_with_side_effects) == result + + setattr(MagicMethodsException, "__call__", _old_method) + + +def test_rsplit_side_effects_none(): + with pytest.raises(AttributeError, match="'NoneType' object has no attribute 'rsplit'"): + mod.do_rsplit_no_args(None) + + +def test_splitlines_side_effects(): + object_with_side_effects = MagicMethodsException(STRING_TO_TAINT) + + with pytest.raises(AttributeError, match="'MagicMethodsException' object has no attribute 'splitlines'"): + mod.do_splitlines_no_arg(object_with_side_effects) + + +def test_splitlines_side_effects_none(): + with pytest.raises(AttributeError, match="'NoneType' object has no attribute 'splitlines'"): + mod.do_splitlines_no_arg(None) + + +def test_str_side_effects(): + object_with_side_effects = MagicMethodsException(STRING_TO_TAINT) + + with pytest.raises(Exception, match="side effect"): + str(object_with_side_effects) + + with pytest.raises(Exception, match="side effect"): + mod.do_str(object_with_side_effects) + + +def test_str_side_effects_none(): + result = mod.do_str(None) + assert result == "None" + + +def test_bytes_side_effects(): + object_with_side_effects = MagicMethodsException(STRING_TO_TAINT) + + with pytest.raises(Exception, match="side effect"): + bytes(object_with_side_effects) + + with pytest.raises(Exception, match="side effect"): + mod.do_bytes(object_with_side_effects) + + +def test_bytes_side_effects_none(): + with pytest.raises(TypeError, match="cannot convert 'NoneType' object to bytes"): + bytes(None) + + with pytest.raises(TypeError, match="cannot convert 'NoneType' object to bytes"): + mod.do_bytes(None) + + +def test_bytes_with_encoding_side_effects(): + object_with_side_effects = MagicMethodsException(STRING_TO_TAINT) + + with pytest.raises(TypeError, match="encoding without a string argument"): + bytes(object_with_side_effects, encoding="utf-8") + + with pytest.raises(TypeError, match="encoding without a string argument"): + mod.do_str_to_bytes(object_with_side_effects) + + +def test_bytes_with_encoding_side_effects_none(): + with pytest.raises(TypeError, match="encoding without a string argument"): + bytes(None, encoding="utf-8") + + with pytest.raises(TypeError, match="encoding without a string argument"): + mod.do_str_to_bytes(None) + + +def test_bytearray_side_effects(): + object_with_side_effects = MagicMethodsException(STRING_TO_TAINT) + + with pytest.raises(Exception, match="side effect"): + bytearray(object_with_side_effects) + + with pytest.raises(Exception, match="side effect"): + mod.do_bytes_to_bytearray(object_with_side_effects) + + +def test_bytearray_side_effects_none(): + if sys.version_info < (3, 8): + msg = "'NoneType' object is not iterable" + else: + msg = "cannot convert 'NoneType' object to bytearray" + + with pytest.raises(TypeError, match=msg): + bytearray(None) + + with pytest.raises(TypeError, match=msg): + mod.do_bytes_to_bytearray(None) + + def test_join_aspect_side_effects(): - string_to_taint = "abc" - object_with_side_effects = MagicMethodsException(string_to_taint) + object_with_side_effects = MagicMethodsException(STRING_TO_TAINT) it = ["a", "b", "c"] @@ -58,9 +181,203 @@ def test_join_aspect_side_effects(): assert result == "abcabc" +def test_join_aspect_side_effects_iterables(): + it = [MagicMethodsException("a"), MagicMethodsException("b"), MagicMethodsException("c")] + + with pytest.raises(TypeError, match="sequence item 0: expected str instance, MagicMethodsException found"): + STRING_TO_TAINT.join(it) + with pytest.raises(TypeError, match="sequence item 0: expected str instance, MagicMethodsException found"): + mod.do_join(STRING_TO_TAINT, it) + + +def test_index_aspect_side_effects(): + def __getitem__(self, a): + return self._data[a] + + _old_method = getattr(MagicMethodsException, "__getitem__", None) + setattr(MagicMethodsException, "__getitem__", __getitem__) + + object_with_side_effects = MagicMethodsException(STRING_TO_TAINT) + result = object_with_side_effects[1] + assert result == "b" + + result_tainted = mod.do_index(object_with_side_effects, 1) + assert result_tainted == result + setattr(MagicMethodsException, "__getitem__", _old_method) + + +def test_slice_aspect_side_effects(): + def __getitem__(self, a): + return self._data[a] + + _old_method = getattr(MagicMethodsException, "__getitem__", None) + setattr(MagicMethodsException, "__getitem__", __getitem__) + + object_with_side_effects = MagicMethodsException(STRING_TO_TAINT) + result = object_with_side_effects[:2] + assert result == "ab" + + result_tainted = mod.do_slice(object_with_side_effects, None, 2, None) + assert result_tainted == result + setattr(MagicMethodsException, "__getitem__", _old_method) + + +def test_bytearray_extend_side_effects(): + _old_method = getattr(MagicMethodsException, "__getitem__", None) + object_with_side_effects = MagicMethodsException(STRING_TO_TAINT) + result = object_with_side_effects.extend(bytearray(b"abc")) + assert result == bytearray(b"abcabc") + + mod.do_bytearray_extend(object_with_side_effects, bytearray(b"abc")) + + +def test_modulo_aspect_side_effects(): + def __str__(self): + return self._data + + _old_method = getattr(MagicMethodsException, "__str__", None) + setattr(MagicMethodsException, "__str__", __str__) + + object_with_side_effects = MagicMethodsException(STRING_TO_TAINT) + result = "aa %s ss" % object_with_side_effects + assert result == "aa %s ss" % "abc" + + result_tainted = mod.do_modulo("aa %s ss", object_with_side_effects) + assert result_tainted == result + + setattr(MagicMethodsException, "__str__", _old_method) + + +def test_modulo_aspect_template_side_effects(): + def __str__(self): + return self._data + + def __mod__(self, a): + return self._data % a + + _old_method_str = getattr(MagicMethodsException, "__str__", None) + _old_method_mod = getattr(MagicMethodsException, "__mod__", None) + setattr(MagicMethodsException, "__str__", __str__) + setattr(MagicMethodsException, "__mod__", __mod__) + + object_with_side_effects = MagicMethodsException("aa %s ss") + result = object_with_side_effects % STRING_TO_TAINT + assert result == "aa %s ss" % "abc" + + result_tainted = mod.do_modulo(object_with_side_effects, STRING_TO_TAINT) + assert result_tainted == result + + setattr(MagicMethodsException, "__str__", _old_method_str) + setattr(MagicMethodsException, "__mod__", _old_method_mod) + + +def test_ljust_aspect_side_effects(): + object_with_side_effects = MagicMethodsException(STRING_TO_TAINT) + result = object_with_side_effects.ljust(10) + assert result == "abc " + + result_tainted = mod.do_ljust(object_with_side_effects, 10) + assert result_tainted == result + + +def test_ljust_aspect_fill_char_side_effects(): + object_with_side_effects = MagicMethodsException(STRING_TO_TAINT) + result = object_with_side_effects.ljust(10, "d") + assert result == "abcddddddd" + + result_tainted = mod.do_ljust_2(object_with_side_effects, 10, "d") + assert result_tainted == result + + +def test_zfill_aspect_side_effects(): + object_with_side_effects = MagicMethodsException(STRING_TO_TAINT) + result = object_with_side_effects.zfill(10) + assert result == "0000000abc" + + result_tainted = mod.do_zfill(object_with_side_effects, 10) + assert result_tainted == result + + +def test_format_aspect_side_effects(): + object_with_side_effects = MagicMethodsException("d: {}, e: {}") + result = object_with_side_effects.format("f", STRING_TO_TAINT) + assert result == "d: f, e: abc" + + result_tainted = mod.do_format(object_with_side_effects, "f", STRING_TO_TAINT) + assert result_tainted == result + + +def test_format_not_str_side_effects(): + object_with_side_effects = MagicMethodsException("aaaa") + + result_tainted = mod.do_format_not_str(object_with_side_effects) + assert result_tainted == "output" + + +def test_format_map_aspect_side_effects(): + object_with_side_effects = MagicMethodsException("d: {data1}, e: {data2}") + result = object_with_side_effects.format_map(dict(data1=STRING_TO_TAINT, data2=STRING_TO_TAINT)) + assert result == "d: abc, e: abc" + + result_tainted = mod.do_format_map(object_with_side_effects, dict(data1=STRING_TO_TAINT, data2=STRING_TO_TAINT)) + assert result_tainted == result + + +def test_taint_pyobject(): + object_with_side_effects = MagicMethodsException(STRING_TO_TAINT) + + result = taint_pyobject( + pyobject=object_with_side_effects, + source_name="test_ospath", + source_value="foo", + source_origin=OriginType.PARAMETER, + ) + assert result._data == STRING_TO_TAINT + + +def test_repr_aspect_side_effects(): + def __repr__(self): + return self._data + + _old_method_repr = getattr(MagicMethodsException, "__repr__", None) + setattr(MagicMethodsException, "__repr__", __repr__) + + object_with_side_effects = MagicMethodsException(STRING_TO_TAINT) + result = repr(object_with_side_effects) + assert result == STRING_TO_TAINT + + result_tainted = mod.do_repr(object_with_side_effects) + assert result_tainted == result + + setattr(MagicMethodsException, "__repr__", _old_method_repr) + + +def test_format_value_aspect_side_effects(): + def __format__(self, *args, **kwargs): + print(args) + print(kwargs) + return self._data + + def __add__(self, b): + return self._data + b + + _old_method_format = getattr(MagicMethodsException, "__format__", None) + setattr(MagicMethodsException, "__format__", __format__) + setattr(MagicMethodsException, "__add__", __add__) + + object_with_side_effects = MagicMethodsException(STRING_TO_TAINT) + b = "bar" + result = f"{object_with_side_effects} + {b} = {object_with_side_effects + b}" + assert result == "abc + bar = abcbar" + + result_tainted = mod.do_fstring(object_with_side_effects, b) + assert result_tainted == result + + setattr(MagicMethodsException, "__format__", _old_method_format) + + def test_encode_aspect_side_effects(): - string_to_taint = "abc" - object_with_side_effects = MagicMethodsException(string_to_taint) + object_with_side_effects = MagicMethodsException(STRING_TO_TAINT) result = mod.do_encode(object_with_side_effects) assert result == b"abc" @@ -69,3 +386,110 @@ def test_encode_aspect_side_effects(): def test_encode_aspect_side_effects_none(): with pytest.raises(AttributeError, match="'NoneType' object has no attribute 'encode'"): mod.do_encode(None) + + +def test_decode_aspect_side_effects(): + object_with_side_effects = MagicMethodsException(b"abc") + + result = mod.do_decode(object_with_side_effects) + assert result == STRING_TO_TAINT + + +def test_decode_aspect_side_effects_none(): + with pytest.raises(AttributeError, match="'NoneType' object has no attribute 'decode'"): + mod.do_decode(None) + + +def test_replace_aspect_side_effects(): + object_with_side_effects = MagicMethodsException(STRING_TO_TAINT) + old_text = "b" + new_text = "fgh" + result = mod.do_replace(object_with_side_effects, old_text, new_text) + assert result == "afghc" + + +def test_replace_aspect_side_effects_none(): + with pytest.raises(AttributeError, match="'NoneType' object has no attribute 'replace'"): + old_text = "b" + new_text = "fgh" + mod.do_replace(None, old_text, new_text) + + +@pytest.mark.parametrize( + "method", + ( + "upper", + "lower", + "swapcase", + "title", + "capitalize", + "casefold", + ), +) +def test_common_aspect_side_effects(method): + object_with_side_effects = MagicMethodsException(STRING_TO_TAINT) + + result = getattr(mod, f"do_{method}")(object_with_side_effects) + assert result == STRING_TO_TAINT + + +@pytest.mark.parametrize( + "method", + ( + "upper", + "lower", + "swapcase", + "title", + "capitalize", + "casefold", + ), +) +def test_common_aspect_side_effects_none(method): + with pytest.raises(AttributeError, match=f"'NoneType' object has no attribute '{method}'"): + getattr(mod, f"do_{method}")(None) + + +def test_translate_aspect_side_effects(): + object_with_side_effects = MagicMethodsException(STRING_TO_TAINT) + + result = mod.do_translate(object_with_side_effects, {"a"}) + assert result == STRING_TO_TAINT + + +def test_translate_aspect_side_effects_none(): + with pytest.raises(AttributeError, match="'NoneType' object has no attribute 'translate'"): + mod.do_translate(None, {"a"}) + + +def test_taint_pyobject_none(): + result = taint_pyobject( + pyobject=None, + source_name="test_ospath", + source_value="foo", + source_origin=OriginType.PARAMETER, + ) + assert result is None + + +def test_taint_pyobject_with_ranges(): + object_with_side_effects = MagicMethodsException(STRING_TO_TAINT) + + result = taint_pyobject_with_ranges(pyobject=object_with_side_effects, ranges=tuple()) + assert result is False + + +def test_taint_pyobject_with_ranges_none(): + result = taint_pyobject_with_ranges(pyobject=None, ranges=tuple()) + assert result is False + + +def test_get_tainted_ranges(): + object_with_side_effects = MagicMethodsException(STRING_TO_TAINT) + + result = get_tainted_ranges(pyobject=object_with_side_effects) + assert result == () + + +def test_get_tainted_ranges_none(): + result = get_tainted_ranges(None) + assert result == () diff --git a/tests/appsec/iast/fixtures/aspects/str_methods.py b/tests/appsec/iast/fixtures/aspects/str_methods.py index a97d4a1c878..b4ce12649e4 100644 --- a/tests/appsec/iast/fixtures/aspects/str_methods.py +++ b/tests/appsec/iast/fixtures/aspects/str_methods.py @@ -215,11 +215,23 @@ def do_bytearray_append(ba): # type: (bytearray) -> bytes return ba -def do_bytearray_extend(ba, b): # type: (bytearray, bytearray) -> None +def do_bytearray_extend(ba: bytearray, b: bytearray) -> bytearray: ba.extend(b) return ba +def do_repr(b: Any) -> str: + return repr(b) + + +def do_str(b: Any) -> str: + return str(b) + + +def do_bytes(b: Any) -> bytes: + return bytes(b) + + def do_bytes_to_str(b): # type: (bytes) -> str return str(b, encoding="utf-8") @@ -392,6 +404,10 @@ def replace(string1): # type: (str) -> str return my_str.replace(c) +def do_format(a: Text, *args: Text) -> Text: + return a.format(*args) + + def do_format_not_str(c): # type: (str) -> str class MyStr(object): @staticmethod @@ -402,6 +418,10 @@ def format(string1): # type: (str) -> str return my_str.format(c) +def do_format_map(a: Text, *args: Text) -> Text: + return a.format_map(*args) + + def do_format_map_not_str(c): # type: (str) -> str class MyStr(object): @staticmethod @@ -847,10 +867,6 @@ def do_args_kwargs_4(format_string, *args_safe, **kwargs_safe): # type: (str, A return format_string.format("1", "2", test_kwarg=3, *args_safe, **kwargs_safe) -def do_format_map(template, mapping): # type: (str, Dict[str, Any]) -> str - return template.format_map(mapping) - - def do_format_key_error(param1): # type: (str, Dict[str, Any]) -> str return "Test {param1}, {param2}".format(param1=param1) # noqa:F524 @@ -1200,3 +1216,7 @@ def do_customspec_ascii(): def do_customspec_formatspec(): c = CustomSpec() return f"{c!s:<20s}" + + +def do_fstring(a, b): + return f"{a} + {b} = {a + b}" diff --git a/tests/appsec/iast/iast_utils_side_effects.py b/tests/appsec/iast/iast_utils_side_effects.py index f55db31ed06..5b71e09625f 100644 --- a/tests/appsec/iast/iast_utils_side_effects.py +++ b/tests/appsec/iast/iast_utils_side_effects.py @@ -7,6 +7,53 @@ def join(self, iterable): def encode(self, *args, **kwargs): return self._data.encode(*args, **kwargs) + def decode(self, *args, **kwargs): + return self._data.decode(*args, **kwargs) + + def split(self, *args, **kwargs): + return self._data.split(*args, **kwargs) + + def extend(self, a): + ba = bytearray(self._data, encoding="utf-8") + ba.extend(a) + return ba + + def ljust(self, width, fill_char=" "): + return self._data.ljust(width, fill_char) + + def zfill(self, width): + return self._data.zfill(width) + + def format(self, *args, **kwargs): + return self._data.format(*args, **kwargs) + + def format_map(self, *args, **kwargs): + return self._data.format_map(*args, **kwargs) + + def upper(self, *args, **kwargs): + return self._data + + def lower(self, *args, **kwargs): + return self._data + + def replace(self, *args, **kwargs): + return self._data.replace(*args, **kwargs) + + def swapcase(self, *args, **kwargs): + return self._data + + def title(self, *args, **kwargs): + return self._data + + def capitalize(self, *args, **kwargs): + return self._data + + def casefold(self, *args, **kwargs): + return self._data + + def translate(self, *args, **kwargs): + return self._data + def __init__(self, data): self._data = data @@ -15,15 +62,17 @@ def __init__(self, data): # return cls # def __setattr__(self, name, value): # raise Exception("side effect") - # def __str__(self): - # raise Exception("side effect") - # def __repr__(self): - # raise Exception("side effect") # def __getattribute__(self, name): # raise Exception("side effect") # def __getattr__(self, name): # raise Exception("side effect") + def __repr__(self): + raise Exception("side effect") + + def __str__(self): + raise Exception("side effect") + def __delattr__(self, name): raise Exception("side effect") From 853d0ef5490f9395103a27fc0fc88429b778c92c Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Tue, 14 May 2024 18:47:36 +0100 Subject: [PATCH 046/104] chore(asm): avoid importing when patching with import hook (#9262) We register an import hook to perform the patching of objects to prevent force-loading modules during bootstrap. This fix a regression introduced by https://github.com/DataDog/dd-trace-py/pull/9246 APPSEC-53121 ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Co-authored-by: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> --- ddtrace/appsec/_common_module_patches.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/ddtrace/appsec/_common_module_patches.py b/ddtrace/appsec/_common_module_patches.py index 71d2fa59b5b..856681e4540 100644 --- a/ddtrace/appsec/_common_module_patches.py +++ b/ddtrace/appsec/_common_module_patches.py @@ -12,6 +12,7 @@ from ddtrace.internal import core from ddtrace.internal._exceptions import BlockingException from ddtrace.internal.logger import get_logger +from ddtrace.internal.module import ModuleWatchdog from ddtrace.settings.asm import config as asm_config from ddtrace.vendor.wrapt import FunctionWrapper from ddtrace.vendor.wrapt import resolve_path @@ -154,11 +155,13 @@ def try_unwrap(module, name): pass -def try_wrap_function_wrapper(module: str, name: str, wrapper: Callable) -> None: - try: - wrap_object(module, name, FunctionWrapper, (wrapper,)) - except (ImportError, AttributeError): - log.debug("ASM patching. Module %s.%s does not exist", module, name) +def try_wrap_function_wrapper(module_name: str, name: str, wrapper: Callable) -> None: + @ModuleWatchdog.after_module_imported(module_name) + def _(module): + try: + wrap_object(module, name, FunctionWrapper, (wrapper,)) + except (ImportError, AttributeError): + log.debug("ASM patching. Module %s.%s does not exist", module_name, name) def wrap_object(module, name, factory, args=(), kwargs=None): From 5148d18ff13f25914ea3bf5efbae9f6695ee3d64 Mon Sep 17 00:00:00 2001 From: Sam Brenner <106700075+sabrenner@users.noreply.github.com> Date: Tue, 14 May 2024 15:31:57 -0400 Subject: [PATCH 047/104] fix(langchain): correctly identify inputs for batch lcel chain calls (#9195) ## Changes Made Both `batch` and `invoke` calls on LCEL chains for LangChain pass through the same traced method, for code re-use. However, the `kwarg` for inputs to batch calls is `inputs`, while for invoke calls is `input`. This change reflects that, and unit tests have been updated ## Motivation Fix possible breaking customer apps (no reports yet as far as I can tell). ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [ ] Title is accurate - [ ] All changes are related to the pull request's stated goal - [ ] Description motivates each change - [ ] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [ ] Testing strategy adequately addresses listed risks - [ ] Change is maintainable (easy to change, telemetry, documentation) - [ ] Release note makes sense to a user of the library - [ ] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [ ] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/contrib/langchain/patch.py | 10 ++++++++-- .../langchain-inputs-checking-f295eefb7053fd47.yaml | 4 ++++ tests/contrib/langchain/test_langchain_community.py | 8 ++++---- 3 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/langchain-inputs-checking-f295eefb7053fd47.yaml diff --git a/ddtrace/contrib/langchain/patch.py b/ddtrace/contrib/langchain/patch.py index 95b817a78ee..019b0c489b3 100644 --- a/ddtrace/contrib/langchain/patch.py +++ b/ddtrace/contrib/langchain/patch.py @@ -731,7 +731,10 @@ def traced_lcel_runnable_sequence(langchain, pin, func, instance, args, kwargs): inputs = None final_output = None try: - inputs = get_argument_value(args, kwargs, 0, "input") + try: + inputs = get_argument_value(args, kwargs, 0, "input") + except ArgumentError: + inputs = get_argument_value(args, kwargs, 0, "inputs") if integration.is_pc_sampled_span(span): if not isinstance(inputs, list): inputs = [inputs] @@ -775,7 +778,10 @@ async def traced_lcel_runnable_sequence_async(langchain, pin, func, instance, ar inputs = None final_output = None try: - inputs = get_argument_value(args, kwargs, 0, "input") + try: + inputs = get_argument_value(args, kwargs, 0, "input") + except ArgumentError: + inputs = get_argument_value(args, kwargs, 0, "inputs") if integration.is_pc_sampled_span(span): if not isinstance(inputs, list): inputs = [inputs] diff --git a/releasenotes/notes/langchain-inputs-checking-f295eefb7053fd47.yaml b/releasenotes/notes/langchain-inputs-checking-f295eefb7053fd47.yaml new file mode 100644 index 00000000000..94e63f03ec5 --- /dev/null +++ b/releasenotes/notes/langchain-inputs-checking-f295eefb7053fd47.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + langchain: This fix resolves an issue where specifying inputs as a keyword argument for batching on chains caused a crash. diff --git a/tests/contrib/langchain/test_langchain_community.py b/tests/contrib/langchain/test_langchain_community.py index 7d5a2fca65e..e1a59a68703 100644 --- a/tests/contrib/langchain/test_langchain_community.py +++ b/tests/contrib/langchain/test_langchain_community.py @@ -1188,7 +1188,7 @@ def test_lcel_chain_batch(langchain_core, langchain_openai, request_vcr): chain = {"topic": langchain_core.runnables.RunnablePassthrough()} | prompt | model | output_parser with request_vcr.use_cassette("lcel_openai_chain_batch.yaml"): - chain.batch(["chickens", "pigs"]) + chain.batch(inputs=["chickens", "pigs"]) @flaky(1735812000) @@ -1205,7 +1205,7 @@ def test_lcel_chain_batch_311(langchain_core, langchain_openai, request_vcr): chain = {"topic": langchain_core.runnables.RunnablePassthrough()} | prompt | model | output_parser with request_vcr.use_cassette("lcel_openai_chain_batch_311.yaml"): - chain.batch(["chickens", "pigs"]) + chain.batch(inputs=["chickens", "pigs"]) @flaky(1735812000) @@ -1246,7 +1246,7 @@ async def test_lcel_chain_batch_async(langchain_core, langchain_openai, request_ chain = {"topic": langchain_core.runnables.RunnablePassthrough()} | prompt | model | output_parser with request_vcr.use_cassette("lcel_openai_chain_batch_async.yaml"): - await chain.abatch(["chickens", "pigs"]) + await chain.abatch(inputs=["chickens", "pigs"]) @pytest.mark.parametrize( @@ -1542,7 +1542,7 @@ def test_llmobs_chain_batch( chain = {"topic": langchain_core.runnables.RunnablePassthrough()} | prompt | model | output_parser self._test_llmobs_chain_invoke( - generate_trace=lambda inputs: chain.batch(["chickens", "pigs"]), + generate_trace=lambda inputs: chain.batch(inputs=["chickens", "pigs"]), request_vcr=request_vcr, mock_llmobs_span_writer=mock_llmobs_span_writer, mock_tracer=mock_tracer, From 6afcedbad8e7bc18c7b618f3660945636394898f Mon Sep 17 00:00:00 2001 From: Romain Komorn <136473744+romainkomorndatadog@users.noreply.github.com> Date: Wed, 15 May 2024 10:20:55 +0100 Subject: [PATCH 048/104] fix(ci_visibility): use the default tracer when enabling CI Visibility (#9016) This switches back to using the default tracer when using CI Visiblity, and fixes an issue where non-test traces were getting lost due to: * going through a non-existent local agent when using `DD_CIVISIBILITY_AGENTLESS_ENABLED=true` * being in a different tracer object which broke trace IDs The historical change behind switching to a separate tracer was so that we would be able to use CI Visibility in this repo, which was causing CI Visibility traces to get mixed up with other traces. Since that would remain a problem the behavior to use a separate tracer with a separate context provider, via the use of `_DD_CIVISIBILITY_USE_CI_CONTEXT_PROVIDER`, is maintained. This also tweaks the `TraceCiVisibilityFilter` to no longer force the `service` of spans to match that of the original under which the tracer was started, which allows for test-level traces with non-test spans (eg: with distributed tracing or `--ddtrace-patch-all` enabled) to properly reflect the right services. ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../requirements/{1ceca0e.txt => 10b8fc3.txt} | 19 +- .../requirements/{2a81450.txt => 1163993.txt} | 19 +- .../requirements/{b908e36.txt => 11bda89.txt} | 11 +- .../requirements/{1e0c0d6.txt => 1252d0f.txt} | 21 +- .../requirements/{cde42be.txt => 144615f.txt} | 22 +- .../requirements/{1e49987.txt => 14ca37f.txt} | 21 +- .../requirements/{14d5757.txt => 17cf97e.txt} | 19 +- .../requirements/{17ecd2b.txt => 18147c2.txt} | 21 +- .../requirements/{764e316.txt => 1b09cab.txt} | 20 +- .../requirements/{1e67852.txt => 1becf29.txt} | 20 +- .../requirements/{77724eb.txt => 1f42cb3.txt} | 20 +- .../requirements/{32540b6.txt => 3af9e27.txt} | 22 +- .../requirements/{1254841.txt => 48eb599.txt} | 19 +- .../requirements/{c426f16.txt => 7800b91.txt} | 12 +- .../requirements/{3e6ea76.txt => 8fd4efc.txt} | 21 +- .../requirements/{6710b57.txt => 91bec06.txt} | 21 +- .../requirements/{18cd4dd.txt => a0aa271.txt} | 11 +- .../requirements/{117f119.txt => d03449e.txt} | 22 +- ddtrace/internal/ci_visibility/filters.py | 1 - ddtrace/internal/ci_visibility/recorder.py | 3 +- ...y-use_default_tracer-8523fd1859dea0da.yaml | 4 + riotfile.py | 2 + tests/ci_visibility/test_ci_visibility.py | 1 - tests/contrib/pytest/test_pytest_snapshot.py | 26 +++ ...ot.test_pytest_with_ddtrace_patch_all.json | 215 ++++++++++++++++++ 25 files changed, 489 insertions(+), 104 deletions(-) rename .riot/requirements/{1ceca0e.txt => 10b8fc3.txt} (55%) rename .riot/requirements/{2a81450.txt => 1163993.txt} (55%) rename .riot/requirements/{b908e36.txt => 11bda89.txt} (69%) rename .riot/requirements/{1e0c0d6.txt => 1252d0f.txt} (51%) rename .riot/requirements/{cde42be.txt => 144615f.txt} (50%) rename .riot/requirements/{1e49987.txt => 14ca37f.txt} (50%) rename .riot/requirements/{14d5757.txt => 17cf97e.txt} (53%) rename .riot/requirements/{17ecd2b.txt => 18147c2.txt} (51%) rename .riot/requirements/{764e316.txt => 1b09cab.txt} (53%) rename .riot/requirements/{1e67852.txt => 1becf29.txt} (52%) rename .riot/requirements/{77724eb.txt => 1f42cb3.txt} (53%) rename .riot/requirements/{32540b6.txt => 3af9e27.txt} (53%) rename .riot/requirements/{1254841.txt => 48eb599.txt} (53%) rename .riot/requirements/{c426f16.txt => 7800b91.txt} (65%) rename .riot/requirements/{3e6ea76.txt => 8fd4efc.txt} (51%) rename .riot/requirements/{6710b57.txt => 91bec06.txt} (51%) rename .riot/requirements/{18cd4dd.txt => a0aa271.txt} (69%) rename .riot/requirements/{117f119.txt => d03449e.txt} (51%) create mode 100644 releasenotes/notes/fix-ci_visibility-use_default_tracer-8523fd1859dea0da.yaml create mode 100644 tests/snapshots/tests.contrib.pytest.test_pytest_snapshot.test_pytest_with_ddtrace_patch_all.json diff --git a/.riot/requirements/1ceca0e.txt b/.riot/requirements/10b8fc3.txt similarity index 55% rename from .riot/requirements/1ceca0e.txt rename to .riot/requirements/10b8fc3.txt index 52bfc2d3b6c..9324ae55f3a 100644 --- a/.riot/requirements/1ceca0e.txt +++ b/.riot/requirements/10b8fc3.txt @@ -2,23 +2,30 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --no-annotate .riot/requirements/1ceca0e.in +# pip-compile --no-annotate .riot/requirements/10b8fc3.in # +anyio==4.3.0 asynctest==0.13.0 attrs==23.2.0 -coverage[toml]==7.4.1 +certifi==2024.2.2 +coverage[toml]==7.4.4 +h11==0.14.0 +httpcore==1.0.5 +httpx==0.27.0 hypothesis==6.45.0 +idna==3.7 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 -msgpack==1.0.7 +msgpack==1.0.8 opentracing==2.4.0 -packaging==23.2 +packaging==24.0 pluggy==1.4.0 py==1.11.0 pytest==6.2.5 -pytest-cov==4.1.0 -pytest-mock==3.12.0 +pytest-cov==5.0.0 +pytest-mock==3.14.0 pytest-randomly==3.15.0 +sniffio==1.3.1 sortedcontainers==2.4.0 toml==0.10.2 diff --git a/.riot/requirements/2a81450.txt b/.riot/requirements/1163993.txt similarity index 55% rename from .riot/requirements/2a81450.txt rename to .riot/requirements/1163993.txt index c9dbb88511a..6ecfee3c50b 100644 --- a/.riot/requirements/2a81450.txt +++ b/.riot/requirements/1163993.txt @@ -2,23 +2,30 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-annotate .riot/requirements/2a81450.in +# pip-compile --no-annotate .riot/requirements/1163993.in # +anyio==4.3.0 asynctest==0.13.0 attrs==23.2.0 -coverage[toml]==7.4.1 +certifi==2024.2.2 +coverage[toml]==7.4.4 +h11==0.14.0 +httpcore==1.0.5 +httpx==0.27.0 hypothesis==6.45.0 +idna==3.7 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 -msgpack==1.0.7 +msgpack==1.0.8 opentracing==2.4.0 -packaging==23.2 +packaging==24.0 pluggy==1.4.0 py==1.11.0 pytest==6.2.5 -pytest-cov==4.1.0 -pytest-mock==3.12.0 +pytest-cov==5.0.0 +pytest-mock==3.14.0 pytest-randomly==3.15.0 +sniffio==1.3.1 sortedcontainers==2.4.0 toml==0.10.2 diff --git a/.riot/requirements/b908e36.txt b/.riot/requirements/11bda89.txt similarity index 69% rename from .riot/requirements/b908e36.txt rename to .riot/requirements/11bda89.txt index d210b2430f5..d33a99885ff 100644 --- a/.riot/requirements/b908e36.txt +++ b/.riot/requirements/11bda89.txt @@ -2,24 +2,31 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/b908e36.in +# pip-compile --no-annotate --resolver=backtracking .riot/requirements/11bda89.in # +anyio==3.7.1 attrs==23.2.0 +certifi==2024.2.2 coverage[toml]==7.2.7 exceptiongroup==1.2.0 +h11==0.14.0 +httpcore==0.17.3 +httpx==0.24.1 hypothesis==6.45.0 +idna==3.7 importlib-metadata==6.7.0 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 msgpack==1.0.5 opentracing==2.4.0 -packaging==23.2 +packaging==24.0 pluggy==1.2.0 pytest==7.4.4 pytest-cov==2.12.0 pytest-mock==2.0.0 pytest-randomly==3.12.0 +sniffio==1.3.1 sortedcontainers==2.4.0 tomli==2.0.1 typing-extensions==4.7.1 diff --git a/.riot/requirements/1e0c0d6.txt b/.riot/requirements/1252d0f.txt similarity index 51% rename from .riot/requirements/1e0c0d6.txt rename to .riot/requirements/1252d0f.txt index 73febff7227..53b83cf7b70 100644 --- a/.riot/requirements/1e0c0d6.txt +++ b/.riot/requirements/1252d0f.txt @@ -2,21 +2,28 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --no-annotate .riot/requirements/1e0c0d6.in +# pip-compile --no-annotate .riot/requirements/1252d0f.in # +anyio==4.3.0 asynctest==0.13.0 attrs==23.2.0 -coverage[toml]==7.4.1 +certifi==2024.2.2 +coverage[toml]==7.4.4 +h11==0.14.0 +httpcore==1.0.5 +httpx==0.27.0 hypothesis==6.45.0 +idna==3.7 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 -msgpack==1.0.7 +msgpack==1.0.8 opentracing==2.4.0 -packaging==23.2 +packaging==24.0 pluggy==1.4.0 -pytest==8.0.0 -pytest-cov==4.1.0 -pytest-mock==3.12.0 +pytest==8.1.1 +pytest-cov==5.0.0 +pytest-mock==3.14.0 pytest-randomly==3.15.0 +sniffio==1.3.1 sortedcontainers==2.4.0 diff --git a/.riot/requirements/cde42be.txt b/.riot/requirements/144615f.txt similarity index 50% rename from .riot/requirements/cde42be.txt rename to .riot/requirements/144615f.txt index 3ec46eba267..ab3ffaf7ef7 100644 --- a/.riot/requirements/cde42be.txt +++ b/.riot/requirements/144615f.txt @@ -2,23 +2,31 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --no-annotate .riot/requirements/cde42be.in +# pip-compile --no-annotate --resolver=backtracking .riot/requirements/144615f.in # +anyio==4.3.0 asynctest==0.13.0 attrs==23.2.0 -coverage[toml]==7.4.1 +certifi==2024.2.2 +coverage[toml]==7.4.4 exceptiongroup==1.2.0 +h11==0.14.0 +httpcore==1.0.5 +httpx==0.27.0 hypothesis==6.45.0 +idna==3.7 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 -msgpack==1.0.7 +msgpack==1.0.8 opentracing==2.4.0 -packaging==23.2 +packaging==24.0 pluggy==1.4.0 -pytest==8.0.0 -pytest-cov==4.1.0 -pytest-mock==3.12.0 +pytest==8.1.1 +pytest-cov==5.0.0 +pytest-mock==3.14.0 pytest-randomly==3.15.0 +sniffio==1.3.1 sortedcontainers==2.4.0 tomli==2.0.1 +typing-extensions==4.11.0 diff --git a/.riot/requirements/1e49987.txt b/.riot/requirements/14ca37f.txt similarity index 50% rename from .riot/requirements/1e49987.txt rename to .riot/requirements/14ca37f.txt index f12227707fa..0d9ec3e2183 100644 --- a/.riot/requirements/1e49987.txt +++ b/.riot/requirements/14ca37f.txt @@ -2,24 +2,33 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --no-annotate .riot/requirements/1e49987.in +# pip-compile --no-annotate --resolver=backtracking .riot/requirements/14ca37f.in # +anyio==4.3.0 asynctest==0.13.0 attrs==23.2.0 -coverage[toml]==7.4.1 +certifi==2024.2.2 +coverage[toml]==7.4.4 +exceptiongroup==1.2.0 +h11==0.14.0 +httpcore==1.0.5 +httpx==0.27.0 hypothesis==6.45.0 +idna==3.7 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 -msgpack==1.0.7 +msgpack==1.0.8 opentracing==2.4.0 -packaging==23.2 +packaging==24.0 pluggy==1.4.0 py==1.11.0 pytest==6.2.5 -pytest-cov==4.1.0 -pytest-mock==3.12.0 +pytest-cov==5.0.0 +pytest-mock==3.14.0 pytest-randomly==3.15.0 +sniffio==1.3.1 sortedcontainers==2.4.0 toml==0.10.2 tomli==2.0.1 +typing-extensions==4.11.0 diff --git a/.riot/requirements/14d5757.txt b/.riot/requirements/17cf97e.txt similarity index 53% rename from .riot/requirements/14d5757.txt rename to .riot/requirements/17cf97e.txt index 36432ffa62c..cec3b324723 100644 --- a/.riot/requirements/14d5757.txt +++ b/.riot/requirements/17cf97e.txt @@ -2,21 +2,28 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --no-annotate .riot/requirements/14d5757.in +# pip-compile --no-annotate .riot/requirements/17cf97e.in # +anyio==4.3.0 asynctest==0.13.0 attrs==23.2.0 -coverage[toml]==7.4.1 +certifi==2024.2.2 +coverage[toml]==7.4.4 +h11==0.14.0 +httpcore==1.0.5 +httpx==0.27.0 hypothesis==6.45.0 +idna==3.7 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 -msgpack==1.0.7 +msgpack==1.0.8 opentracing==2.4.0 -packaging==23.2 +packaging==24.0 pluggy==1.4.0 pytest==7.4.4 -pytest-cov==4.1.0 -pytest-mock==3.12.0 +pytest-cov==5.0.0 +pytest-mock==3.14.0 pytest-randomly==3.15.0 +sniffio==1.3.1 sortedcontainers==2.4.0 diff --git a/.riot/requirements/17ecd2b.txt b/.riot/requirements/18147c2.txt similarity index 51% rename from .riot/requirements/17ecd2b.txt rename to .riot/requirements/18147c2.txt index bb56b30b3e9..5c5f8a6a0fd 100644 --- a/.riot/requirements/17ecd2b.txt +++ b/.riot/requirements/18147c2.txt @@ -2,21 +2,28 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-annotate .riot/requirements/17ecd2b.in +# pip-compile --no-annotate .riot/requirements/18147c2.in # +anyio==4.3.0 asynctest==0.13.0 attrs==23.2.0 -coverage[toml]==7.4.1 +certifi==2024.2.2 +coverage[toml]==7.4.4 +h11==0.14.0 +httpcore==1.0.5 +httpx==0.27.0 hypothesis==6.45.0 +idna==3.7 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 -msgpack==1.0.7 +msgpack==1.0.8 opentracing==2.4.0 -packaging==23.2 +packaging==24.0 pluggy==1.4.0 -pytest==8.0.0 -pytest-cov==4.1.0 -pytest-mock==3.12.0 +pytest==8.1.1 +pytest-cov==5.0.0 +pytest-mock==3.14.0 pytest-randomly==3.15.0 +sniffio==1.3.1 sortedcontainers==2.4.0 diff --git a/.riot/requirements/764e316.txt b/.riot/requirements/1b09cab.txt similarity index 53% rename from .riot/requirements/764e316.txt rename to .riot/requirements/1b09cab.txt index ebece799c14..a8fd716e3c9 100644 --- a/.riot/requirements/764e316.txt +++ b/.riot/requirements/1b09cab.txt @@ -2,24 +2,32 @@ # This file is autogenerated by pip-compile with Python 3.8 # by the following command: # -# pip-compile --no-annotate .riot/requirements/764e316.in +# pip-compile --config=pyproject.toml --no-annotate .riot/requirements/1b09cab.in # +anyio==4.3.0 attrs==23.2.0 -coverage[toml]==7.4.1 +certifi==2024.2.2 +coverage[toml]==7.4.4 exceptiongroup==1.2.0 +h11==0.14.0 +httpcore==1.0.5 +httpx==0.27.0 hypothesis==6.45.0 -importlib-metadata==7.0.1 +idna==3.7 +importlib-metadata==7.1.0 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 -msgpack==1.0.7 +msgpack==1.0.8 opentracing==2.4.0 -packaging==23.2 +packaging==24.0 pluggy==1.4.0 pytest==7.4.4 pytest-cov==2.12.0 pytest-mock==2.0.0 pytest-randomly==3.15.0 +sniffio==1.3.1 sortedcontainers==2.4.0 tomli==2.0.1 -zipp==3.17.0 +typing-extensions==4.11.0 +zipp==3.18.1 diff --git a/.riot/requirements/1e67852.txt b/.riot/requirements/1becf29.txt similarity index 52% rename from .riot/requirements/1e67852.txt rename to .riot/requirements/1becf29.txt index 21bd5692752..9a18af7d058 100644 --- a/.riot/requirements/1e67852.txt +++ b/.riot/requirements/1becf29.txt @@ -2,23 +2,31 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --no-annotate .riot/requirements/1e67852.in +# pip-compile --no-annotate --resolver=backtracking .riot/requirements/1becf29.in # +anyio==4.3.0 asynctest==0.13.0 attrs==23.2.0 -coverage[toml]==7.4.1 +certifi==2024.2.2 +coverage[toml]==7.4.4 exceptiongroup==1.2.0 +h11==0.14.0 +httpcore==1.0.5 +httpx==0.27.0 hypothesis==6.45.0 +idna==3.7 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 -msgpack==1.0.7 +msgpack==1.0.8 opentracing==2.4.0 -packaging==23.2 +packaging==24.0 pluggy==1.4.0 pytest==7.4.4 -pytest-cov==4.1.0 -pytest-mock==3.12.0 +pytest-cov==5.0.0 +pytest-mock==3.14.0 pytest-randomly==3.15.0 +sniffio==1.3.1 sortedcontainers==2.4.0 tomli==2.0.1 +typing-extensions==4.11.0 diff --git a/.riot/requirements/77724eb.txt b/.riot/requirements/1f42cb3.txt similarity index 53% rename from .riot/requirements/77724eb.txt rename to .riot/requirements/1f42cb3.txt index 327cbb26b12..2d3467ff27a 100644 --- a/.riot/requirements/77724eb.txt +++ b/.riot/requirements/1f42cb3.txt @@ -2,24 +2,32 @@ # This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --no-annotate .riot/requirements/77724eb.in +# pip-compile --config=pyproject.toml --no-annotate .riot/requirements/1f42cb3.in # +anyio==4.3.0 attrs==23.2.0 -coverage[toml]==7.4.1 +certifi==2024.2.2 +coverage[toml]==7.4.4 exceptiongroup==1.2.0 +h11==0.14.0 +httpcore==1.0.5 +httpx==0.27.0 hypothesis==6.45.0 -importlib-metadata==7.0.1 +idna==3.7 +importlib-metadata==7.1.0 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 -msgpack==1.0.7 +msgpack==1.0.8 opentracing==2.4.0 -packaging==23.2 +packaging==24.0 pluggy==1.4.0 pytest==7.4.4 pytest-cov==2.12.0 pytest-mock==2.0.0 pytest-randomly==3.15.0 +sniffio==1.3.1 sortedcontainers==2.4.0 tomli==2.0.1 -zipp==3.17.0 +typing-extensions==4.11.0 +zipp==3.18.1 diff --git a/.riot/requirements/32540b6.txt b/.riot/requirements/3af9e27.txt similarity index 53% rename from .riot/requirements/32540b6.txt rename to .riot/requirements/3af9e27.txt index 346a6890b0f..09231897bd8 100644 --- a/.riot/requirements/32540b6.txt +++ b/.riot/requirements/3af9e27.txt @@ -2,24 +2,32 @@ # This file is autogenerated by pip-compile with Python 3.8 # by the following command: # -# pip-compile --no-annotate .riot/requirements/32540b6.in +# pip-compile --no-annotate .riot/requirements/3af9e27.in # +anyio==4.3.0 attrs==23.2.0 -coverage[toml]==7.4.1 +certifi==2024.2.2 +coverage[toml]==7.4.4 exceptiongroup==1.2.0 +h11==0.14.0 +httpcore==1.0.5 +httpx==0.27.0 hypothesis==6.45.0 -importlib-metadata==7.0.1 +idna==3.7 +importlib-metadata==7.1.0 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 -msgpack==1.0.7 +msgpack==1.0.8 opentracing==2.4.0 -packaging==23.2 +packaging==24.0 pluggy==1.4.0 -pytest==8.0.0 +pytest==8.1.1 pytest-cov==2.12.0 pytest-mock==2.0.0 pytest-randomly==3.15.0 +sniffio==1.3.1 sortedcontainers==2.4.0 tomli==2.0.1 -zipp==3.17.0 +typing-extensions==4.11.0 +zipp==3.18.1 diff --git a/.riot/requirements/1254841.txt b/.riot/requirements/48eb599.txt similarity index 53% rename from .riot/requirements/1254841.txt rename to .riot/requirements/48eb599.txt index 3639eb9fb05..cf67ac7deb3 100644 --- a/.riot/requirements/1254841.txt +++ b/.riot/requirements/48eb599.txt @@ -2,21 +2,28 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-annotate .riot/requirements/1254841.in +# pip-compile --no-annotate .riot/requirements/48eb599.in # +anyio==4.3.0 asynctest==0.13.0 attrs==23.2.0 -coverage[toml]==7.4.1 +certifi==2024.2.2 +coverage[toml]==7.4.4 +h11==0.14.0 +httpcore==1.0.5 +httpx==0.27.0 hypothesis==6.45.0 +idna==3.7 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 -msgpack==1.0.7 +msgpack==1.0.8 opentracing==2.4.0 -packaging==23.2 +packaging==24.0 pluggy==1.4.0 pytest==7.4.4 -pytest-cov==4.1.0 -pytest-mock==3.12.0 +pytest-cov==5.0.0 +pytest-mock==3.14.0 pytest-randomly==3.15.0 +sniffio==1.3.1 sortedcontainers==2.4.0 diff --git a/.riot/requirements/c426f16.txt b/.riot/requirements/7800b91.txt similarity index 65% rename from .riot/requirements/c426f16.txt rename to .riot/requirements/7800b91.txt index 4ee102b0707..b64794723bc 100644 --- a/.riot/requirements/c426f16.txt +++ b/.riot/requirements/7800b91.txt @@ -2,24 +2,32 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/c426f16.in +# pip-compile --no-annotate --resolver=backtracking .riot/requirements/7800b91.in # +anyio==3.7.1 attrs==23.2.0 +certifi==2024.2.2 coverage==7.2.7 +exceptiongroup==1.2.0 +h11==0.14.0 +httpcore==0.17.3 +httpx==0.24.1 hypothesis==6.45.0 +idna==3.7 importlib-metadata==6.7.0 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 msgpack==1.0.5 opentracing==2.4.0 -packaging==23.2 +packaging==24.0 pluggy==1.2.0 py==1.11.0 pytest==6.2.5 pytest-cov==2.9.0 pytest-mock==2.0.0 pytest-randomly==3.12.0 +sniffio==1.3.1 sortedcontainers==2.4.0 toml==0.10.2 typing-extensions==4.7.1 diff --git a/.riot/requirements/3e6ea76.txt b/.riot/requirements/8fd4efc.txt similarity index 51% rename from .riot/requirements/3e6ea76.txt rename to .riot/requirements/8fd4efc.txt index 9f2f450e61f..a7217c530cd 100644 --- a/.riot/requirements/3e6ea76.txt +++ b/.riot/requirements/8fd4efc.txt @@ -2,24 +2,33 @@ # This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --no-annotate .riot/requirements/3e6ea76.in +# pip-compile --config=pyproject.toml --no-annotate .riot/requirements/8fd4efc.in # +anyio==4.3.0 attrs==23.2.0 -coverage==7.4.1 +certifi==2024.2.2 +coverage==7.4.4 +exceptiongroup==1.2.0 +h11==0.14.0 +httpcore==1.0.5 +httpx==0.27.0 hypothesis==6.45.0 -importlib-metadata==7.0.1 +idna==3.7 +importlib-metadata==7.1.0 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 -msgpack==1.0.7 +msgpack==1.0.8 opentracing==2.4.0 -packaging==23.2 +packaging==24.0 pluggy==1.4.0 py==1.11.0 pytest==6.2.5 pytest-cov==2.9.0 pytest-mock==2.0.0 pytest-randomly==3.15.0 +sniffio==1.3.1 sortedcontainers==2.4.0 toml==0.10.2 -zipp==3.17.0 +typing-extensions==4.11.0 +zipp==3.18.1 diff --git a/.riot/requirements/6710b57.txt b/.riot/requirements/91bec06.txt similarity index 51% rename from .riot/requirements/6710b57.txt rename to .riot/requirements/91bec06.txt index abc0c7a3a4d..15f0875ec11 100644 --- a/.riot/requirements/6710b57.txt +++ b/.riot/requirements/91bec06.txt @@ -2,24 +2,33 @@ # This file is autogenerated by pip-compile with Python 3.8 # by the following command: # -# pip-compile --no-annotate .riot/requirements/6710b57.in +# pip-compile --config=pyproject.toml --no-annotate .riot/requirements/91bec06.in # +anyio==4.3.0 attrs==23.2.0 -coverage==7.4.1 +certifi==2024.2.2 +coverage==7.4.4 +exceptiongroup==1.2.0 +h11==0.14.0 +httpcore==1.0.5 +httpx==0.27.0 hypothesis==6.45.0 -importlib-metadata==7.0.1 +idna==3.7 +importlib-metadata==7.1.0 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 -msgpack==1.0.7 +msgpack==1.0.8 opentracing==2.4.0 -packaging==23.2 +packaging==24.0 pluggy==1.4.0 py==1.11.0 pytest==6.2.5 pytest-cov==2.9.0 pytest-mock==2.0.0 pytest-randomly==3.15.0 +sniffio==1.3.1 sortedcontainers==2.4.0 toml==0.10.2 -zipp==3.17.0 +typing-extensions==4.11.0 +zipp==3.18.1 diff --git a/.riot/requirements/18cd4dd.txt b/.riot/requirements/a0aa271.txt similarity index 69% rename from .riot/requirements/18cd4dd.txt rename to .riot/requirements/a0aa271.txt index 2cf6ca97907..c88c04d4be7 100644 --- a/.riot/requirements/18cd4dd.txt +++ b/.riot/requirements/a0aa271.txt @@ -2,24 +2,31 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/18cd4dd.in +# pip-compile --no-annotate --resolver=backtracking .riot/requirements/a0aa271.in # +anyio==3.7.1 attrs==23.2.0 +certifi==2024.2.2 coverage[toml]==7.2.7 exceptiongroup==1.2.0 +h11==0.14.0 +httpcore==0.17.3 +httpx==0.24.1 hypothesis==6.45.0 +idna==3.7 importlib-metadata==6.7.0 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 msgpack==1.0.5 opentracing==2.4.0 -packaging==23.2 +packaging==24.0 pluggy==1.2.0 pytest==7.4.4 pytest-cov==2.12.0 pytest-mock==2.0.0 pytest-randomly==3.12.0 +sniffio==1.3.1 sortedcontainers==2.4.0 tomli==2.0.1 typing-extensions==4.7.1 diff --git a/.riot/requirements/117f119.txt b/.riot/requirements/d03449e.txt similarity index 51% rename from .riot/requirements/117f119.txt rename to .riot/requirements/d03449e.txt index b71c5320c7d..39414e29954 100644 --- a/.riot/requirements/117f119.txt +++ b/.riot/requirements/d03449e.txt @@ -2,24 +2,32 @@ # This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --no-annotate .riot/requirements/117f119.in +# pip-compile --config=pyproject.toml --no-annotate .riot/requirements/d03449e.in # +anyio==4.3.0 attrs==23.2.0 -coverage[toml]==7.4.1 +certifi==2024.2.2 +coverage[toml]==7.4.4 exceptiongroup==1.2.0 +h11==0.14.0 +httpcore==1.0.5 +httpx==0.27.0 hypothesis==6.45.0 -importlib-metadata==7.0.1 +idna==3.7 +importlib-metadata==7.1.0 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 -msgpack==1.0.7 +msgpack==1.0.8 opentracing==2.4.0 -packaging==23.2 +packaging==24.0 pluggy==1.4.0 -pytest==8.0.0 +pytest==8.1.1 pytest-cov==2.12.0 pytest-mock==2.0.0 pytest-randomly==3.15.0 +sniffio==1.3.1 sortedcontainers==2.4.0 tomli==2.0.1 -zipp==3.17.0 +typing-extensions==4.11.0 +zipp==3.18.1 diff --git a/ddtrace/internal/ci_visibility/filters.py b/ddtrace/internal/ci_visibility/filters.py index a6e4db86bd6..c90e7324533 100644 --- a/ddtrace/internal/ci_visibility/filters.py +++ b/ddtrace/internal/ci_visibility/filters.py @@ -34,7 +34,6 @@ def process_trace(self, trace): local_root.context.sampling_priority = AUTO_KEEP for span in trace: span.set_tags(self._tags) - span.service = self._service span.set_tag_str(ci.LIBRARY_VERSION, ddtrace.__version__) return trace diff --git a/ddtrace/internal/ci_visibility/recorder.py b/ddtrace/internal/ci_visibility/recorder.py index 93725a48404..fffc97527a6 100644 --- a/ddtrace/internal/ci_visibility/recorder.py +++ b/ddtrace/internal/ci_visibility/recorder.py @@ -10,6 +10,7 @@ from typing import Union # noqa:F401 from uuid import uuid4 +import ddtrace from ddtrace import Tracer from ddtrace import config as ddconfig from ddtrace.contrib import trace_utils @@ -161,7 +162,7 @@ def __init__(self, tracer=None, config=None, service=None): # Create a new CI tracer self.tracer = Tracer(context_provider=CIContextProvider()) else: - self.tracer = Tracer() + self.tracer = ddtrace.tracer # Partial traces are required for ITR to work in suite-level skipping for long test sessions, but we # assume that a tracer is already configured if it's been passed in. diff --git a/releasenotes/notes/fix-ci_visibility-use_default_tracer-8523fd1859dea0da.yaml b/releasenotes/notes/fix-ci_visibility-use_default_tracer-8523fd1859dea0da.yaml new file mode 100644 index 00000000000..a8c8eceda78 --- /dev/null +++ b/releasenotes/notes/fix-ci_visibility-use_default_tracer-8523fd1859dea0da.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + CI Visibility: fixes that traces were not properly being sent in agentless mode, and were otherwise not properly attached to the test that started them diff --git a/riotfile.py b/riotfile.py index c5143c26777..4d2b39c307e 100644 --- a/riotfile.py +++ b/riotfile.py @@ -1580,6 +1580,7 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): "msgpack": latest, "more_itertools": "<8.11.0", "pytest-mock": "==2.0.0", + "httpx": latest, }, venvs=[ Venv( @@ -1607,6 +1608,7 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): "msgpack": latest, "asynctest": "==0.13.0", "more_itertools": "<8.11.0", + "httpx": latest, }, ), ], diff --git a/tests/ci_visibility/test_ci_visibility.py b/tests/ci_visibility/test_ci_visibility.py index 4354c116ab1..c6fd79fb546 100644 --- a/tests/ci_visibility/test_ci_visibility.py +++ b/tests/ci_visibility/test_ci_visibility.py @@ -66,7 +66,6 @@ def test_filters_test_spans(): # Root span in trace is a test trace = [root_test_span] assert trace_filter.process_trace(trace) == trace - assert root_test_span.service == "test-service" assert root_test_span.get_tag(ci.LIBRARY_VERSION) == ddtrace.__version__ assert root_test_span.get_tag("hello") == "world" assert root_test_span.context.dd_origin == ci.CI_APP_TEST_ORIGIN diff --git a/tests/contrib/pytest/test_pytest_snapshot.py b/tests/contrib/pytest/test_pytest_snapshot.py index 80d4bbadb21..d73ab68377e 100644 --- a/tests/contrib/pytest/test_pytest_snapshot.py +++ b/tests/contrib/pytest/test_pytest_snapshot.py @@ -28,6 +28,7 @@ "duration", "start", ] +SNAPSHOT_IGNORES_PATCH_ALL = SNAPSHOT_IGNORES + ["meta.http.useragent"] SNAPSHOT_IGNORES_ITR_COVERAGE = ["metrics.test.source.start", "metrics.test.source.end", "meta.test.source.file"] @@ -118,3 +119,28 @@ def test_add_two_number_list(): return_value=_CIVisibilitySettings(False, False, False, False), ): subprocess.run(["ddtrace-run", "coverage", "run", "--include=nothing.py", "-m", "pytest", "--ddtrace"]) + + @snapshot(ignores=SNAPSHOT_IGNORES_PATCH_ALL) + def test_pytest_with_ddtrace_patch_all(self): + call_httpx = """ + import httpx + + def call_httpx(): + return httpx.get("http://localhost:9126/bad_path.cgi") + """ + self.testdir.makepyfile(call_httpx=call_httpx) + test_call_httpx = """ + from call_httpx import call_httpx + + def test_call_urllib(): + r = call_httpx() + assert r.status_code == 404 + """ + self.testdir.makepyfile(test_call_httpx=test_call_httpx) + self.testdir.chdir() + with override_env(dict(DD_API_KEY="foobar.baz", DD_CIVISIBILITY_ITR_ENABLED="false")): + with mock.patch( + "ddtrace.internal.ci_visibility.recorder.CIVisibility._check_settings_api", + return_value=_CIVisibilitySettings(False, False, False, False), + ): + subprocess.run(["pytest", "--ddtrace", "--ddtrace-patch-all"]) diff --git a/tests/snapshots/tests.contrib.pytest.test_pytest_snapshot.test_pytest_with_ddtrace_patch_all.json b/tests/snapshots/tests.contrib.pytest.test_pytest_snapshot.test_pytest_with_ddtrace_patch_all.json new file mode 100644 index 00000000000..6c842085cc1 --- /dev/null +++ b/tests/snapshots/tests.contrib.pytest.test_pytest_snapshot.test_pytest_with_ddtrace_patch_all.json @@ -0,0 +1,215 @@ +[[ + { + "name": "pytest.test_session", + "service": "pytest", + "resource": "pytest.test_session", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "661fce1b00000000", + "component": "pytest", + "language": "python", + "library_version": "2.9.0.dev80+gae109804d.d20240417", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "5.15.49-linuxkit-pr", + "runtime-id": "0a9eec171fca451babccd0136aa32c67", + "runtime.name": "CPython", + "runtime.version": "3.8.16", + "span.kind": "test", + "test.code_coverage.enabled": "false", + "test.command": "pytest --ddtrace --ddtrace-patch-all", + "test.framework": "pytest", + "test.framework_version": "8.1.1", + "test.itr.tests_skipping.enabled": "false", + "test.status": "pass", + "test_session_id": "10252982646231086668", + "type": "test_session_end" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 44747 + }, + "duration": 175777694, + "start": 1713360411000016472 + }, + { + "name": "pytest.test_module", + "service": "pytest", + "resource": "pytest.test_module", + "trace_id": 0, + "span_id": 2, + "parent_id": 1, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "661fce1b00000000", + "component": "pytest", + "language": "python", + "library_version": "2.9.0.dev80+gae109804d.d20240417", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "5.15.49-linuxkit-pr", + "runtime.name": "CPython", + "runtime.version": "3.8.16", + "span.kind": "test", + "test.code_coverage.enabled": "false", + "test.command": "pytest --ddtrace --ddtrace-patch-all", + "test.framework": "pytest", + "test.framework_version": "8.1.1", + "test.itr.tests_skipping.enabled": "false", + "test.module": "", + "test.module_path": "", + "test.status": "pass", + "test_module_id": "5968154422818595882", + "test_session_id": "10252982646231086668", + "type": "test_module_end" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1 + }, + "duration": 54086708, + "start": 1713360411121294000 + }, + { + "name": "pytest.test_suite", + "service": "pytest", + "resource": "pytest.test_suite", + "trace_id": 0, + "span_id": 3, + "parent_id": 2, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "661fce1b00000000", + "component": "pytest", + "language": "python", + "library_version": "2.9.0.dev80+gae109804d.d20240417", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "5.15.49-linuxkit-pr", + "runtime.name": "CPython", + "runtime.version": "3.8.16", + "span.kind": "test", + "test.command": "pytest --ddtrace --ddtrace-patch-all", + "test.framework": "pytest", + "test.framework_version": "8.1.1", + "test.module": "", + "test.module_path": "", + "test.status": "pass", + "test.suite": "test_call_httpx.py", + "test_module_id": "5968154422818595882", + "test_session_id": "10252982646231086668", + "test_suite_id": "937891247795205250", + "type": "test_suite_end" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1 + }, + "duration": 53712375, + "start": 1713360411121370750 + }], +[ + { + "name": "pytest.test", + "service": "pytest", + "resource": "test_call_httpx.py::test_call_urllib", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "test", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.origin": "ciapp-test", + "_dd.p.dm": "-0", + "_dd.p.tid": "661fce1b00000000", + "component": "pytest", + "language": "python", + "library_version": "2.9.0.dev80+gae109804d.d20240417", + "os.architecture": "aarch64", + "os.platform": "Linux", + "os.version": "5.15.49-linuxkit-pr", + "runtime-id": "0a9eec171fca451babccd0136aa32c67", + "runtime.name": "CPython", + "runtime.version": "3.8.16", + "span.kind": "test", + "test.command": "pytest --ddtrace --ddtrace-patch-all", + "test.framework": "pytest", + "test.framework_version": "8.1.1", + "test.module": "", + "test.module_path": "", + "test.name": "test_call_urllib", + "test.source.file": "test_call_httpx.py", + "test.status": "pass", + "test.suite": "test_call_httpx.py", + "test.type": "test", + "test_module_id": "5968154422818595882", + "test_session_id": "10252982646231086668", + "test_suite_id": "937891247795205250", + "type": "test" + }, + "metrics": { + "_dd.py.partial_flush": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 44747, + "test.source.end": 6, + "test.source.start": 3 + }, + "duration": 54019542, + "start": 1713360411121402458 + }], +[ + { + "name": "http.request", + "service": "", + "resource": "http.request", + "trace_id": 2, + "span_id": 1, + "parent_id": 0, + "type": "http", + "error": 0, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "661fce1b00000000", + "component": "httpx", + "http.method": "GET", + "http.status_code": "404", + "http.url": "http://localhost:9126/bad_path.cgi", + "http.useragent": "this_should_never_match", + "language": "python", + "out.host": "localhost", + "runtime-id": "0a9eec171fca451babccd0136aa32c67", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 44747 + }, + "duration": 2077333, + "start": 1713360411171812125 + }]] From f31c7859c9233c2ebcb4eff53c1eeb890b5c460b Mon Sep 17 00:00:00 2001 From: Emmett Butler <723615+emmettbutler@users.noreply.github.com> Date: Wed, 15 May 2024 07:22:17 -0700 Subject: [PATCH 049/104] ci(profiling): avoid testing fork method because it consistently times out in CI (#9264) This change removes `fork` from the set of tested multiprocessing methods in a `profiling` test. This method has been observed timing out regularly in main-branch CI runs: https://app.circleci.com/pipelines/github/DataDog/dd-trace-py/61423/workflows/4952b2ad-97c9-4b50-a3fd-dedcd2534787/jobs/3846581 https://app.circleci.com/pipelines/github/DataDog/dd-trace-py/61415/workflows/6ab71a9a-076e-47b5-bb29-585969135ead/jobs/3846579 https://app.circleci.com/pipelines/github/DataDog/dd-trace-py/61402/workflows/585938d6-5a9a-4d6d-a882-2d372a4db55d/jobs/3846576 ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [ ] Title is accurate - [ ] All changes are related to the pull request's stated goal - [ ] Description motivates each change - [ ] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [ ] Testing strategy adequately addresses listed risks - [ ] Change is maintainable (easy to change, telemetry, documentation) - [ ] Release note makes sense to a user of the library - [ ] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [ ] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Co-authored-by: Brett Langdon --- tests/profiling/test_main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/profiling/test_main.py b/tests/profiling/test_main.py index 7b17dd3a8f8..ba123363a06 100644 --- a/tests/profiling/test_main.py +++ b/tests/profiling/test_main.py @@ -85,7 +85,7 @@ def test_fork_gevent(monkeypatch): @pytest.mark.parametrize( "method", - set(methods) - {"forkserver"}, # flaky + set(methods) - {"forkserver", "fork"}, # flaky ) def test_multiprocessing(method, tmp_path, monkeypatch): filename = str(tmp_path / "pprof") From 9eccfec4bc5cbf8742da06946c44eed682eb75b1 Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Wed, 15 May 2024 11:57:24 -0400 Subject: [PATCH 050/104] fix(internal): fix CI Visibility usage of the core API (#9162) Fixes some small inconsistencies with the core api usage in CI Visibility. 1. A call to `dispatch_with_results` when no value is expected to be returned (small performance optimization to avoid building a results dictionary when it isn't needed). 2. A call to `dispatch_with_results` which would not return the underlying listener value a. (I _think_ it would always return a falsey value since it should have been returning the `core._MissingEvent` `EventResult` which is falsey and which has a value of `None`... ?) I am not sure which tests should be evaluated/updated, since I am surprised there weren't any failing tests before, there _should_ have been. ## Checklist - [x] Change(s) are motivated and described in the PR description - [ ] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [ ] Title is accurate - [ ] All changes are related to the pull request's stated goal - [ ] Description motivates each change - [ ] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [ ] Testing strategy adequately addresses listed risks - [ ] Change is maintainable (easy to change, telemetry, documentation) - [ ] Release note makes sense to a user of the library - [ ] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [ ] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/ext/ci_visibility/api.py | 14 +- ddtrace/internal/ci_visibility/recorder.py | 11 +- .../api/test_ext_ci_visibility_api.py | 126 ++++++++++++++++++ tests/ci_visibility/test_ci_visibility.py | 11 +- tests/ci_visibility/util.py | 82 ++++++++++++ 5 files changed, 222 insertions(+), 22 deletions(-) diff --git a/ddtrace/ext/ci_visibility/api.py b/ddtrace/ext/ci_visibility/api.py index ef8ae2e98a2..6671237bf22 100644 --- a/ddtrace/ext/ci_visibility/api.py +++ b/ddtrace/ext/ci_visibility/api.py @@ -235,12 +235,6 @@ def finish( "ci_visibility.session.finish", (CISession.FinishArgs(item_id, force_finish_children, override_status),) ) - @staticmethod - @_catch_and_log_exceptions - def get_settings(item_id: Optional[CISessionId] = None): - log.debug("Getting settings for session %s", item_id) - core.dispatch_with_results("ci_visibility.session.get_settings", (item_id,)) - @staticmethod @_catch_and_log_exceptions def get_known_tests(item_id: Optional[CISessionId] = None): @@ -309,11 +303,13 @@ def mark_itr_forced_run(item_id: Union[CISuiteId, CITestId]): @staticmethod @_catch_and_log_exceptions - def is_item_itr_skippable(item_id: Union[CISuiteId, CITestId]): + def is_item_itr_skippable(item_id: Union[CISuiteId, CITestId]) -> bool: """Skippable items are not currently tied to a test session, so no session ID is passed""" log.debug("Getting skippable items") - skippable_items = core.dispatch_with_results("ci_visibility.itr.is_item_skippable", (item_id,)) - return skippable_items["skippable_items"] + is_item_skippable: Optional[bool] = core.dispatch_with_results( + "ci_visibility.itr.is_item_skippable", (item_id,) + ).is_item_skippable.value + return bool(is_item_skippable) class AddCoverageArgs(NamedTuple): item_id: Union[_CIVisibilityChildItemIdBase, _CIVisibilityRootItemIdBase] diff --git a/ddtrace/internal/ci_visibility/recorder.py b/ddtrace/internal/ci_visibility/recorder.py index fffc97527a6..93efc2fc913 100644 --- a/ddtrace/internal/ci_visibility/recorder.py +++ b/ddtrace/internal/ci_visibility/recorder.py @@ -921,18 +921,11 @@ def _on_finish_session(finish_args: CISession.FinishArgs): session.finish(finish_args.force_finish_children, finish_args.override_status) -@_requires_civisibility_enabled -def _on_get_session_settings(session_id: CISessionId): - log.debug("Handling get session settings for session id %s", session_id) - return CIVisibility.get_session_settings(session_id) - - def _register_session_handlers(): log.debug("Registering session handlers") core.on("ci_visibility.session.discover", _on_discover_session) core.on("ci_visibility.session.start", _on_start_session) core.on("ci_visibility.session.finish", _on_finish_session) - core.on("ci_visibility.session.get_settings", _on_get_session_settings) @_requires_civisibility_enabled @@ -1140,7 +1133,7 @@ def _on_itr_mark_forced_run(item_id: Union[CISuiteId, CITestId]) -> None: @_requires_civisibility_enabled def _on_itr_is_item_skippable(item_id: Union[CISuiteId, CITestId]) -> bool: """Skippable items are fetched as part CIVisibility.enable(), so they are assumed to be available.""" - log.debug("Handling get skippable items") + log.debug("Handling is item skippable for item id %s", item_id) if not isinstance(item_id, (CISuiteId, CITestId)): log.warning("Only suites or tests can be skippable, not %s", type(item_id)) @@ -1156,7 +1149,7 @@ def _on_itr_is_item_skippable(item_id: Union[CISuiteId, CITestId]) -> bool: def _register_itr_handlers(): log.debug("Registering ITR-related handlers") core.on("ci_visibility.itr.finish_skipped_by_itr", _on_itr_finish_item_skipped) - core.on("ci_visibility.itr.is_item_skippable", _on_itr_is_item_skippable) + core.on("ci_visibility.itr.is_item_skippable", _on_itr_is_item_skippable, "is_item_skippable") core.on("ci_visibility.itr.mark_unskippable", _on_itr_mark_unskippable) core.on("ci_visibility.itr.mark_forced_run", _on_itr_mark_forced_run) diff --git a/tests/ci_visibility/api/test_ext_ci_visibility_api.py b/tests/ci_visibility/api/test_ext_ci_visibility_api.py index cee2d8b443c..5f62b7d0461 100644 --- a/tests/ci_visibility/api/test_ext_ci_visibility_api.py +++ b/tests/ci_visibility/api/test_ext_ci_visibility_api.py @@ -3,7 +3,10 @@ import pytest +from ddtrace.ext.ci_visibility import api from ddtrace.ext.ci_visibility.api import CISourceFileInfo +from ddtrace.internal.ci_visibility import CIVisibility +from tests.ci_visibility.util import set_up_mock_civisibility class TestCISourceFileInfo: @@ -53,3 +56,126 @@ def test_source_file_info_enforces_start_line_less_than_end_line(self): with pytest.raises(ValueError): # start_line cannot be None if end_line is provided _ = CISourceFileInfo(Path("/absolute/path/my_file_name"), end_line=1) + + +class TestCIITRMixin: + """Tests whether or not skippable tests and suites are correctly identified + + Note: these tests do not bother discovering a session as the ITR functionality currently does not rely on sessions. + """ + + def test_api_is_item_itr_skippable_test_level(self): + with set_up_mock_civisibility( + itr_enabled=True, + skipping_enabled=True, + suite_skipping_mode=False, + skippable_items={ + "skippable_module/suite.py": ["skippable_test"], + }, + ): + CIVisibility.enable() + + assert CIVisibility.enabled is True + assert CIVisibility._instance._suite_skipping_mode is False + + session_id = api.CISessionId("session_id") + + skippable_module_id = api.CIModuleId(session_id, "skippable_module") + + skippable_suite_id = api.CISuiteId(skippable_module_id, "suite.py") + skippable_test_id = api.CITestId(skippable_suite_id, "skippable_test") + non_skippable_test_id = api.CITestId(skippable_suite_id, "non_skippable_test") + + non_skippable_suite_id = api.CISuiteId(skippable_module_id, "non_skippable_suite.py") + non_skippable_suite_skippable_test_id = api.CITestId(non_skippable_suite_id, "skippable_test") + + assert api.CITest.is_item_itr_skippable(skippable_test_id) is True + assert api.CITest.is_item_itr_skippable(non_skippable_test_id) is False + assert api.CITest.is_item_itr_skippable(non_skippable_suite_skippable_test_id) is False + + CIVisibility.disable() + + def test_api_is_item_itr_skippable_false_when_skipping_disabled_test_level(self): + with set_up_mock_civisibility( + itr_enabled=True, + skipping_enabled=False, + suite_skipping_mode=False, + skippable_items={ + "skippable_module/suite.py": ["skippable_test"], + }, + ): + CIVisibility.enable() + + assert CIVisibility.enabled is True + assert CIVisibility._instance._suite_skipping_mode is False + + session_id = api.CISessionId("session_id") + + skippable_module_id = api.CIModuleId(session_id, "skippable_module") + + skippable_suite_id = api.CISuiteId(skippable_module_id, "suite.py") + skippable_test_id = api.CITestId(skippable_suite_id, "skippable_test") + non_skippable_test_id = api.CITestId(skippable_suite_id, "non_skippable_test") + + non_skippable_suite_id = api.CISuiteId(skippable_module_id, "non_skippable_suite.py") + non_skippable_suite_skippable_test_id = api.CITestId(non_skippable_suite_id, "skippable_test") + + assert api.CITest.is_item_itr_skippable(skippable_test_id) is False + assert api.CITest.is_item_itr_skippable(non_skippable_test_id) is False + assert api.CITest.is_item_itr_skippable(non_skippable_suite_skippable_test_id) is False + + CIVisibility.disable() + + def test_api_is_item_itr_skippable_suite_level(self): + with set_up_mock_civisibility( + itr_enabled=True, + skipping_enabled=True, + suite_skipping_mode=True, + skippable_items=["skippable_module/skippable_suite.py"], + ): + CIVisibility.enable() + + assert CIVisibility.enabled is True + assert CIVisibility._instance._suite_skipping_mode is True + + session_id = api.CISessionId("session_id") + + skippable_module_id = api.CIModuleId(session_id, "skippable_module") + skippable_suite_id = api.CISuiteId(skippable_module_id, "skippable_suite.py") + non_skippable_suite_id = api.CISuiteId(skippable_module_id, "non_skippable_suite.py") + + non_skippable_module_id = api.CIModuleId(session_id, "non_skippable_module") + non_skippable_module_skippable_suite_id = api.CISuiteId(non_skippable_module_id, "skippable_suite.py") + + assert api.CISuite.is_item_itr_skippable(skippable_suite_id) is True + assert api.CISuite.is_item_itr_skippable(non_skippable_suite_id) is False + assert api.CISuite.is_item_itr_skippable(non_skippable_module_skippable_suite_id) is False + + CIVisibility.disable() + + def test_api_is_item_itr_skippable_false_when_skipping_disabled_suite_level(self): + with set_up_mock_civisibility( + itr_enabled=True, + skipping_enabled=False, + suite_skipping_mode=True, + skippable_items=["skippable_module/skippable_suite.py"], + ): + CIVisibility.enable() + + assert CIVisibility.enabled is True + assert CIVisibility._instance._suite_skipping_mode is True + + session_id = api.CISessionId("session_id") + + skippable_module_id = api.CIModuleId(session_id, "skippable_module") + skippable_suite_id = api.CISuiteId(skippable_module_id, "skippable_suite.py") + non_skippable_suite_id = api.CISuiteId(skippable_module_id, "non_skippable_suite.py") + + non_skippable_module_id = api.CIModuleId(session_id, "non_skippable_module") + non_skippable_module_skippable_suite_id = api.CISuiteId(non_skippable_module_id, "skippable_suite.py") + + assert api.CISuite.is_item_itr_skippable(skippable_suite_id) is False + assert api.CISuite.is_item_itr_skippable(non_skippable_suite_id) is False + assert api.CISuite.is_item_itr_skippable(non_skippable_module_skippable_suite_id) is False + + CIVisibility.disable() diff --git a/tests/ci_visibility/test_ci_visibility.py b/tests/ci_visibility/test_ci_visibility.py index c6fd79fb546..221a8d7be46 100644 --- a/tests/ci_visibility/test_ci_visibility.py +++ b/tests/ci_visibility/test_ci_visibility.py @@ -1673,10 +1673,10 @@ class TestIsITRSkippable: m3_s2_t7 = api.CITestId(m3_s2, "test_6[param3]") def _get_all_suite_ids(self): - return {getattr(self, suite_id) for suite_id in vars(self) if re.match(r"^m\d_s\d$", suite_id)} + return {getattr(self, suite_id) for suite_id in vars(self.__class__) if re.match(r"^m\d_s\d$", suite_id)} def _get_all_test_ids(self): - return {getattr(self, test_id) for test_id in vars(self) if re.match(r"^m\d_s\d_t\d$", test_id)} + return {getattr(self, test_id) for test_id in vars(self.__class__) if re.match(r"^m\d_s\d_t\d$", test_id)} def test_is_item_itr_skippable_test_level(self): with mock.patch.object(CIVisibility, "enabled", True), mock.patch.object( @@ -1722,12 +1722,15 @@ def test_is_item_itr_skippable_suite_level(self): mock_instance._tests_to_skip = defaultdict(list) mock_instance._suite_skipping_mode = True + expected_skippable_suite_ids = {self.m1_s1, self.m2_s1, self.m2_s2, self.m3_s1} + expected_non_skippable_suite_ids = self._get_all_suite_ids() - set(expected_skippable_suite_ids) + # Check skippable suites are correct - for suite_id in [self.m1_s1, self.m2_s1, self.m2_s2, self.m3_s1]: + for suite_id in expected_skippable_suite_ids: assert CIVisibility.is_item_itr_skippable(suite_id) is True # Check non-skippable suites are correct - for suite_id in [self.m1_s2, self.m3_s2]: + for suite_id in expected_non_skippable_suite_ids: assert CIVisibility.is_item_itr_skippable(suite_id) is False # Check all tests are not skippable diff --git a/tests/ci_visibility/util.py b/tests/ci_visibility/util.py index 05ae67e9de4..58af0f57882 100644 --- a/tests/ci_visibility/util.py +++ b/tests/ci_visibility/util.py @@ -1,8 +1,15 @@ +from collections import defaultdict from contextlib import contextmanager +from unittest import mock import ddtrace from ddtrace.internal.ci_visibility import DEFAULT_CI_VISIBILITY_SERVICE +from ddtrace.internal.ci_visibility.git_client import METADATA_UPLOAD_STATUS +from ddtrace.internal.ci_visibility.git_client import CIVisibilityGitClient +from ddtrace.internal.ci_visibility.recorder import CIVisibility +from ddtrace.internal.ci_visibility.recorder import _CIVisibilitySettings from tests.utils import DummyCIVisibilityWriter +from tests.utils import override_env @contextmanager @@ -22,3 +29,78 @@ def _get_default_civisibility_ddconfig(): }, ) return new_ddconfig + + +@contextmanager +def set_up_mock_civisibility( + use_agentless: bool = True, + coverage_enabled: bool = False, + skipping_enabled: bool = False, + itr_enabled: bool = False, + require_git: bool = False, + suite_skipping_mode: bool = False, + skippable_items=None, +): + """This is a one-stop-shop that patches all parts of CI Visibility for testing. + + Its purpose is to allow testers to call CIVisibility.enable() without side effects and with predictable results + while still exercising most of the internal (eg: non-API, non-subprocess-executing) code. + + It prevents: + * requests to settings and skippable API endpoints + * git client instantiation and use (skipping git metadata upload) + + It additionally raises NotImplementedErrors to try and alert callers if they are trying to do something that should + be mocked, but isn't. + """ + + def _fake_fetch_tests_to_skip(*args, **kwargs): + if skippable_items is None: + if suite_skipping_mode: + CIVisibility._instance._test_suites_to_skip = [] + else: + CIVisibility._instance._tests_to_skip = defaultdict(list) + else: + if suite_skipping_mode: + CIVisibility._instance._test_suites_to_skip = skippable_items + else: + CIVisibility._instance._tests_to_skip = skippable_items + + def _mock_upload_git_metadata(obj, **kwargs): + obj._metadata_upload_status = METADATA_UPLOAD_STATUS.SUCCESS + + env_overrides = { + "DD_CIVISIBILITY_AGENTLESS_ENABLED": "False", + "DD_SERVICE": "civis-test-service", + "DD_ENV": "civis-test-env", + } + if use_agentless: + env_overrides.update({"DD_API_KEY": "civisfakeapikey", "DD_CIVISIBILITY_AGENTLESS_ENABLED": "true"}) + if suite_skipping_mode: + env_overrides.update({"_DD_CIVISIBILITY_ITR_SUITE_MODE": "true"}) + + with override_env(env_overrides), mock.patch( + "ddtrace.internal.ci_visibility.recorder.CIVisibility._check_enabled_features", + return_value=_CIVisibilitySettings( + coverage_enabled=coverage_enabled, + skipping_enabled=skipping_enabled, + require_git=require_git, + itr_enabled=itr_enabled, + ), + ), mock.patch( + "ddtrace.internal.ci_visibility.recorder.CIVisibility._fetch_tests_to_skip", + side_effect=_fake_fetch_tests_to_skip, + ), mock.patch.multiple( + CIVisibilityGitClient, + _get_repository_url=classmethod(lambda *args, **kwargs: "git@github.com:TestDog/dd-test-py.git"), + _is_shallow_repository=classmethod(lambda *args, **kwargs: False), + _get_latest_commits=classmethod(lambda *args, **kwwargs: ["latest1", "latest2"]), + _search_commits=classmethod(lambda *args: ["latest1", "searched1", "searched2"]), + _get_filtered_revisions=classmethod(lambda *args, **kwargs: "revision1\nrevision2"), + _upload_packfiles=classmethod(lambda *args, **kwargs: None), + upload_git_metadata=_mock_upload_git_metadata, + _do_request=NotImplementedError, + ), mock.patch( + "ddtrace.internal.ci_visibility.recorder._do_request", side_effect=NotImplementedError + ): + yield From 1cb3487f7d2396f8c41419aed0a6860f9313de03 Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Wed, 15 May 2024 12:51:30 -0400 Subject: [PATCH 051/104] chore: update changelog for version 2.8.4 (#9186) - [x] update changelog for version 2.8.4 --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47047c4a1c9..c8e7f92afcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ Changelogs for versions not listed here can be found at https://github.com/DataDog/dd-trace-py/releases +--- + +## 2.8.4 + + +### Bug Fixes + +- telemetry: This fix resolves an issue when using `pytest` + `gevent` where the telemetry writer was eager initialized by `pytest` entrypoints loading of our plugin causing a potential dead lock. + + --- ## 2.7.10 From 5897cabe7651f30b0f865ed211c84e1316b49628 Mon Sep 17 00:00:00 2001 From: William Conti <58711692+wconti27@users.noreply.github.com> Date: Wed, 15 May 2024 14:17:02 -0400 Subject: [PATCH 052/104] fix(grpc): fix segfault with grpc.aio streaming responses (#9233) This PR fixes a few issues with the `grpc aio` integration. Most notably, the integration was causing segfaults when wrapping async stream responses, most likely since these spans were never being finished. This issue was uncovered when customers upgraded their `google-api-core` dependencies to `2.17.0`; with this upgrade, the package changed many grpc calls to use async streaming. In addition to fixing the segfault, this PR also fixes the Pin object to be correctly placed on the grpcio module. Fixes #9139 ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../contrib/grpc/aio_client_interceptor.py | 54 ++++- ddtrace/contrib/grpc/patch.py | 7 +- ddtrace/contrib/grpc/utils.py | 28 +++ ...gle-api-core-upgrade-abbc097a46b5d032.yaml | 4 + .../grpc_aio/hellostreamingworld_pb2.py | 35 ++++ .../grpc_aio/hellostreamingworld_pb2.pyi | 19 ++ .../grpc_aio/hellostreamingworld_pb2_grpc.py | 77 +++++++ tests/contrib/grpc_aio/test_grpc_aio.py | 188 +++++++++++++++++- 8 files changed, 387 insertions(+), 25 deletions(-) create mode 100644 releasenotes/notes/fix-grpc-bug-caused-by-google-api-core-upgrade-abbc097a46b5d032.yaml create mode 100644 tests/contrib/grpc_aio/hellostreamingworld_pb2.py create mode 100644 tests/contrib/grpc_aio/hellostreamingworld_pb2.pyi create mode 100644 tests/contrib/grpc_aio/hellostreamingworld_pb2_grpc.py diff --git a/ddtrace/contrib/grpc/aio_client_interceptor.py b/ddtrace/contrib/grpc/aio_client_interceptor.py index 04113a42767..fb48ff54252 100644 --- a/ddtrace/contrib/grpc/aio_client_interceptor.py +++ b/ddtrace/contrib/grpc/aio_client_interceptor.py @@ -26,12 +26,16 @@ from ...ext import SpanKind from ...ext import SpanTypes from ...internal.compat import to_unicode +from ...internal.logger import get_logger from ...propagation.http import HTTPPropagator from .. import trace_utils from ..grpc import constants from ..grpc import utils +log = get_logger(__name__) + + def create_aio_client_interceptors(pin, host, port): # type: (Pin, str, int) -> Tuple[aio.ClientInterceptor, ...] return ( @@ -52,7 +56,7 @@ def _handle_add_callback(call, callback): callback(call) -def _done_callback(span, code, details): +def _done_callback_unary(span, code, details): # type: (Span, grpc.StatusCode, str) -> Callable[[aio.Call], None] def func(call): # type: (aio.Call) -> None @@ -61,15 +65,45 @@ def func(call): # Handle server-side error in unary response RPCs if code != grpc.StatusCode.OK: - _handle_error(span, call, code, details) + _handle_error(span, code, details) + finally: + span.finish() + + return func + + +def _done_callback_stream(span): + # type: (Span) -> Callable[[aio.Call], None] + def func(call): + # type: (aio.Call) -> None + try: + if call.done(): + # check to ensure code and details are not already set, in which case this span + # is an error span and already has all error tags from `_handle_cancelled_error` + code_tag = span.get_tag(constants.GRPC_STATUS_CODE_KEY) + details_tag = span.get_tag(ERROR_MSG) + if not code_tag or not details_tag: + # we need to call __repr__ as we cannot call code() or details() since they are both async + code, details = utils._parse_rpc_repr_string(call.__repr__(), grpc) + + span.set_tag_str(constants.GRPC_STATUS_CODE_KEY, to_unicode(code)) + + # Handle server-side error in unary response RPCs + if code != grpc.StatusCode.OK: + _handle_error(span, code, details) + else: + log.warning("Grpc call has not completed, unable to set status code and details on span.") + except ValueError: + # ValueError is thrown from _parse_rpc_repr_string + log.warning("Unable to parse async grpc string for status code and details.") finally: span.finish() return func -def _handle_error(span, call, code, details): - # type: (Span, aio.Call, grpc.StatusCode, str) -> None +def _handle_error(span, code, details): + # type: (Span, grpc.StatusCode, str) -> None span.error = 1 span.set_tag_str(ERROR_MSG, details) span.set_tag_str(ERROR_TYPE, to_unicode(code)) @@ -160,13 +194,13 @@ async def _wrap_stream_response( span: Span, ) -> ResponseIterableType: try: + _handle_add_callback(call, _done_callback_stream(span)) async for response in call: yield response - code = await call.code() - details = await call.details() - # NOTE: The callback is registered after the iteration is done, - # otherwise `call.code()` and `call.details()` block indefinitely. - _handle_add_callback(call, _done_callback(span, code, details)) + except StopAsyncIteration: + # Callback will handle span finishing + _handle_cancelled_error() + raise except aio.AioRpcError as rpc_error: # NOTE: We can also handle the error in done callbacks, # but reuse this error handling function used in unary response RPCs. @@ -192,7 +226,7 @@ async def _wrap_unary_response( # NOTE: As both `code` and `details` are available after the RPC is done (= we get `call` object), # and we can't call awaitable functions inside the non-async callback, # there is no other way but to register the callback here. - _handle_add_callback(call, _done_callback(span, code, details)) + _handle_add_callback(call, _done_callback_unary(span, code, details)) return call except aio.AioRpcError as rpc_error: # NOTE: `AioRpcError` is raised in `await continuation(...)` diff --git a/ddtrace/contrib/grpc/patch.py b/ddtrace/contrib/grpc/patch.py index 834c460dfa9..b942558820a 100644 --- a/ddtrace/contrib/grpc/patch.py +++ b/ddtrace/contrib/grpc/patch.py @@ -208,7 +208,7 @@ def _unpatch_aio_server(): def _client_channel_interceptor(wrapped, instance, args, kwargs): channel = wrapped(*args, **kwargs) - pin = Pin.get_from(channel) + pin = Pin.get_from(constants.GRPC_PIN_MODULE_CLIENT) if not pin or not pin.enabled(): return channel @@ -219,11 +219,10 @@ def _client_channel_interceptor(wrapped, instance, args, kwargs): def _aio_client_channel_interceptor(wrapped, instance, args, kwargs): - channel = wrapped(*args, **kwargs) + pin = Pin.get_from(GRPC_AIO_PIN_MODULE_CLIENT) - pin = Pin.get_from(channel) if not pin or not pin.enabled(): - return channel + return wrapped(*args, **kwargs) (host, port) = utils._parse_target_from_args(args, kwargs) diff --git a/ddtrace/contrib/grpc/utils.py b/ddtrace/contrib/grpc/utils.py index 4589dcf1876..376e30a7043 100644 --- a/ddtrace/contrib/grpc/utils.py +++ b/ddtrace/contrib/grpc/utils.py @@ -1,5 +1,6 @@ import ipaddress import logging +import re from ddtrace.internal.compat import parse @@ -81,3 +82,30 @@ def _parse_target_from_args(args, kwargs): return hostname, port except ValueError: log.warning("Malformed target '%s'.", target) + + +def _parse_rpc_repr_string(rpc_string, module): + # Define the regular expression patterns to extract status and details + status_pattern = r"status\s*=\s*StatusCode\.(\w+)" + details_pattern = r'details\s*=\s*"([^"]*)"' + + # Search for the status and details in the input string + status_match = re.search(status_pattern, rpc_string) + details_match = re.search(details_pattern, rpc_string) + + if not status_match or not details_match: + raise ValueError("Unable to parse grpc status or details repr string") + + # Extract the status and details from the matches + status_str = status_match.group(1) + details = details_match.group(1) + + # Convert the status string to a grpc.StatusCode object + try: + code = module.StatusCode[status_str] + except KeyError: + code = None + raise ValueError(f"Invalid grpc status code: {status_str}") + + # Return the status code and details + return code, details diff --git a/releasenotes/notes/fix-grpc-bug-caused-by-google-api-core-upgrade-abbc097a46b5d032.yaml b/releasenotes/notes/fix-grpc-bug-caused-by-google-api-core-upgrade-abbc097a46b5d032.yaml new file mode 100644 index 00000000000..1d7946afed3 --- /dev/null +++ b/releasenotes/notes/fix-grpc-bug-caused-by-google-api-core-upgrade-abbc097a46b5d032.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + fix(grpc): This fix a bug in the grpc.aio support specific to streaming responses. diff --git a/tests/contrib/grpc_aio/hellostreamingworld_pb2.py b/tests/contrib/grpc_aio/hellostreamingworld_pb2.py new file mode 100644 index 00000000000..a017b55c76b --- /dev/null +++ b/tests/contrib/grpc_aio/hellostreamingworld_pb2.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: hellostreamingworld.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder + + +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( + b'\n\x19hellostreamingworld.proto\x12\x13hellostreamingworld"3\n\x0cHelloRequest\x12\x0c\n\x04' + + b'name\x18\x01 \x01(\t\x12\x15\n\rnum_greetings\x18\x02 \x01(\t"\x1d\n\nHelloReply\x12\x0f\n\x07' + + b"message\x18\x01 \x01(\t2b\n\x0cMultiGreeter\x12R\n\x08sayHello\x12!.hellostreamingworld.HelloRequest" + + b'\x1a\x1f.hellostreamingworld.HelloReply"\x00\x30\x01\x42\x0f\n\x07\x65x.grpc\xa2\x02\x03HSWb\x06proto3' +) + + +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "hellostreamingworld_pb2", globals()) +if _descriptor._USE_C_DESCRIPTORS is False: + DESCRIPTOR._options = None + DESCRIPTOR._serialized_options = b"\n\007ex.grpc\242\002\003HSW" + _HELLOREQUEST._serialized_start = 50 # noqa: F821 + _HELLOREQUEST._serialized_end = 101 # noqa: F821 + _HELLOREPLY._serialized_start = 103 # noqa: F821 + _HELLOREPLY._serialized_end = 132 # noqa: F821 + _MULTIGREETER._serialized_start = 134 # noqa: F821 + _MULTIGREETER._serialized_end = 232 # noqa: F821 +# @@protoc_insertion_point(module_scope) diff --git a/tests/contrib/grpc_aio/hellostreamingworld_pb2.pyi b/tests/contrib/grpc_aio/hellostreamingworld_pb2.pyi new file mode 100644 index 00000000000..35d878e7a7e --- /dev/null +++ b/tests/contrib/grpc_aio/hellostreamingworld_pb2.pyi @@ -0,0 +1,19 @@ +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from typing import ClassVar as _ClassVar, Optional as _Optional + +DESCRIPTOR: _descriptor.FileDescriptor + +class HelloReply(_message.Message): + __slots__ = ["message"] + MESSAGE_FIELD_NUMBER: _ClassVar[int] + message: str + def __init__(self, message: _Optional[str] = ...) -> None: ... + +class HelloRequest(_message.Message): + __slots__ = ["name", "num_greetings"] + NAME_FIELD_NUMBER: _ClassVar[int] + NUM_GREETINGS_FIELD_NUMBER: _ClassVar[int] + name: str + num_greetings: str + def __init__(self, name: _Optional[str] = ..., num_greetings: _Optional[str] = ...) -> None: ... diff --git a/tests/contrib/grpc_aio/hellostreamingworld_pb2_grpc.py b/tests/contrib/grpc_aio/hellostreamingworld_pb2_grpc.py new file mode 100644 index 00000000000..b09944b790b --- /dev/null +++ b/tests/contrib/grpc_aio/hellostreamingworld_pb2_grpc.py @@ -0,0 +1,77 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +from tests.contrib.grpc_aio import hellostreamingworld_pb2 as hellostreamingworld__pb2 + + +class MultiGreeterStub(object): + """The greeting service definition.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.sayHello = channel.unary_stream( + "/hellostreamingworld.MultiGreeter/sayHello", + request_serializer=hellostreamingworld__pb2.HelloRequest.SerializeToString, + response_deserializer=hellostreamingworld__pb2.HelloReply.FromString, + ) + + +class MultiGreeterServicer(object): + """The greeting service definition.""" + + def sayHello(self, request, context): + """Sends multiple greetings""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details("Method not implemented!") + raise NotImplementedError("Method not implemented!") + + +def add_MultiGreeterServicer_to_server(servicer, server): + rpc_method_handlers = { + "sayHello": grpc.unary_stream_rpc_method_handler( + servicer.sayHello, + request_deserializer=hellostreamingworld__pb2.HelloRequest.FromString, + response_serializer=hellostreamingworld__pb2.HelloReply.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler("hellostreamingworld.MultiGreeter", rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + +# This class is part of an EXPERIMENTAL API. +class MultiGreeter(object): + """The greeting service definition.""" + + @staticmethod + def sayHello( + request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None, + ): + return grpc.experimental.unary_stream( + request, + target, + "/hellostreamingworld.MultiGreeter/sayHello", + hellostreamingworld__pb2.HelloRequest.SerializeToString, + hellostreamingworld__pb2.HelloReply.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + ) diff --git a/tests/contrib/grpc_aio/test_grpc_aio.py b/tests/contrib/grpc_aio/test_grpc_aio.py index 601df100417..39671459b8c 100644 --- a/tests/contrib/grpc_aio/test_grpc_aio.py +++ b/tests/contrib/grpc_aio/test_grpc_aio.py @@ -17,18 +17,25 @@ from ddtrace.contrib.grpc import unpatch from ddtrace.contrib.grpc.patch import GRPC_AIO_PIN_MODULE_CLIENT from ddtrace.contrib.grpc.patch import GRPC_AIO_PIN_MODULE_SERVER +from ddtrace.contrib.grpc.utils import _parse_rpc_repr_string import ddtrace.vendor.packaging.version as packaging_version from tests.contrib.grpc.hello_pb2 import HelloReply from tests.contrib.grpc.hello_pb2 import HelloRequest from tests.contrib.grpc.hello_pb2_grpc import HelloServicer from tests.contrib.grpc.hello_pb2_grpc import HelloStub from tests.contrib.grpc.hello_pb2_grpc import add_HelloServicer_to_server +from tests.contrib.grpc_aio.hellostreamingworld_pb2 import HelloReply as HelloReplyStream +from tests.contrib.grpc_aio.hellostreamingworld_pb2 import HelloRequest as HelloRequestStream +from tests.contrib.grpc_aio.hellostreamingworld_pb2_grpc import MultiGreeterServicer +from tests.contrib.grpc_aio.hellostreamingworld_pb2_grpc import MultiGreeterStub +from tests.contrib.grpc_aio.hellostreamingworld_pb2_grpc import add_MultiGreeterServicer_to_server from tests.utils import DummyTracer from tests.utils import assert_is_measured from tests.utils import override_config _GRPC_PORT = 50531 +NUMBER_OF_REPLY = 10 class _CoroHelloServicer(HelloServicer): @@ -150,6 +157,12 @@ def SayHelloRepeatedly(self, request_iterator, context): yield HelloReply(message="Good bye") +class Greeter(MultiGreeterServicer): + async def sayHello(self, request, context): + for i in range(NUMBER_OF_REPLY): + yield HelloReplyStream(message=f"Hello number {i}, {request.name}!") + + class DummyClientInterceptor(aio.UnaryUnaryClientInterceptor): async def intercept_unary_unary(self, continuation, client_call_details, request): undone_call = await continuation(client_call_details, request) @@ -175,6 +188,24 @@ def tracer(): tracer.pop() +@pytest.fixture +async def async_server_info(request, tracer, event_loop): + _ServerInfo = namedtuple("_ServerInfo", ("target", "abort_supported")) + _server = grpc.aio.server() + add_MultiGreeterServicer_to_server(Greeter(), _server) + _servicer = request.param + target = f"localhost:{_GRPC_PORT}" + _server.add_insecure_port(target) + # interceptor can not catch AbortError for sync servicer + abort_supported = not isinstance(_servicer, (_SyncHelloServicer,)) + + await _server.start() + wait_task = event_loop.create_task(_server.wait_for_termination()) + yield _ServerInfo(target, abort_supported) + await _server.stop(grace=None) + await wait_task + + # `pytest_asyncio.fixture` cannot be used # with pytest-asyncio 0.16.0 which is the latest version available for Python3.6. @pytest.fixture @@ -183,7 +214,6 @@ async def server_info(request, tracer, event_loop): tracer fixture is imported to make sure the tracer is pinned to the modules. """ _ServerInfo = namedtuple("_ServerInfo", ("target", "abort_supported")) - _servicer = request.param target = f"localhost:{_GRPC_PORT}" _server = _create_server(_servicer, target) @@ -208,16 +238,16 @@ def _get_spans(tracer): return tracer._writer.spans -def _check_client_span(span, service, method_name, method_kind): +def _check_client_span(span, service, method_name, method_kind, resource="helloworld.Hello"): assert_is_measured(span) assert span.name == "grpc" - assert span.resource == "/helloworld.Hello/{}".format(method_name) + assert span.resource == "/{}/{}".format(resource, method_name) assert span.service == service assert span.error == 0 assert span.span_type == "grpc" - assert span.get_tag("grpc.method.path") == "/helloworld.Hello/{}".format(method_name) - assert span.get_tag("grpc.method.package") == "helloworld" - assert span.get_tag("grpc.method.service") == "Hello" + assert span.get_tag("grpc.method.path") == "/{}/{}".format(resource, method_name) + assert span.get_tag("grpc.method.package") == resource.split(".")[0] + assert span.get_tag("grpc.method.service") == resource.split(".")[1] assert span.get_tag("grpc.method.name") == method_name assert span.get_tag("grpc.method.kind") == method_kind assert span.get_tag("grpc.status.code") == "StatusCode.OK" @@ -228,16 +258,16 @@ def _check_client_span(span, service, method_name, method_kind): assert span.get_tag("span.kind") == "client" -def _check_server_span(span, service, method_name, method_kind): +def _check_server_span(span, service, method_name, method_kind, resource="helloworld.Hello"): assert_is_measured(span) assert span.name == "grpc" - assert span.resource == "/helloworld.Hello/{}".format(method_name) + assert span.resource == "/{}/{}".format(resource, method_name) assert span.service == service assert span.error == 0 assert span.span_type == "grpc" - assert span.get_tag("grpc.method.path") == "/helloworld.Hello/{}".format(method_name) - assert span.get_tag("grpc.method.package") == "helloworld" - assert span.get_tag("grpc.method.service") == "Hello" + assert span.get_tag("grpc.method.path") == "/{}/{}".format(resource, method_name) + assert span.get_tag("grpc.method.package") == resource.split(".")[0] + assert span.get_tag("grpc.method.service") == resource.split(".")[1] assert span.get_tag("grpc.method.name") == method_name assert span.get_tag("grpc.method.kind") == method_kind assert span.get_tag("component") == "grpc_aio_server" @@ -1005,3 +1035,139 @@ async def test_client_streaming(server_info, tracer): out, err, status, _ = ddtrace_run_python_code_in_subprocess(code, env=env) assert status == 0, (err.decode(), out.decode()) assert err == b"", err.decode() + + +class StreamInterceptor(grpc.aio.UnaryStreamClientInterceptor): + async def intercept_unary_stream(self, continuation, call_details, request): + response_iterator = await continuation(call_details, request) + return response_iterator + + +async def run_streaming_example(server_info, use_generator=False): + i = 0 + async with grpc.aio.insecure_channel(server_info.target, interceptors=[StreamInterceptor()]) as channel: + stub = MultiGreeterStub(channel) + + # Read from an async generator + if use_generator: + async for response in stub.sayHello(HelloRequestStream(name="you")): + assert response.message == "Hello number {}, you!".format(i) + i += 1 + + # Direct read from the stub + else: + hello_stream = stub.sayHello(HelloRequestStream(name="will")) + while True: + response = await hello_stream.read() + if response == grpc.aio.EOF: + break + assert response.message == "Hello number {}, will!".format(i) + i += 1 + + +@pytest.mark.asyncio +@pytest.mark.skip( + "Bug/error from grpc when adding an async streaming client interceptor throws StopAsyncIteration. Issue can be \ + found at: https://github.com/DataDog/dd-trace-py/issues/9139" +) +@pytest.mark.parametrize("async_server_info", [_CoroHelloServicer()], indirect=True) +async def test_async_streaming_direct_read(async_server_info, tracer): + await run_streaming_example(async_server_info) + + spans = _get_spans(tracer) + assert len(spans) == 2 + client_span, server_span = spans + + # No error because cancelled after execution + _check_client_span(client_span, "grpc-aio-client", "SayHelloRepeatedly", "bidi_streaming") + _check_server_span(server_span, "grpc-aio-server", "SayHelloRepeatedly", "bidi_streaming") + + +@pytest.mark.asyncio +@pytest.mark.parametrize("async_server_info", [_CoroHelloServicer()], indirect=True) +async def test_async_streaming_generator(async_server_info, tracer): + await run_streaming_example(async_server_info, use_generator=True) + + spans = _get_spans(tracer) + assert len(spans) == 2 + client_span, server_span = spans + + # No error because cancelled after execution + _check_client_span( + client_span, "grpc-aio-client", "sayHello", "server_streaming", "hellostreamingworld.MultiGreeter" + ) + _check_server_span( + server_span, "grpc-aio-server", "sayHello", "server_streaming", "hellostreamingworld.MultiGreeter" + ) + + +repr_test_cases = [ + { + "rpc_string": 'status = StatusCode.OK, details = "Everything is fine"', + "expected_code": grpc.StatusCode.OK, + "expected_details": "Everything is fine", + "expect_error": False, + }, + { + "rpc_string": 'status = StatusCode.ABORTED, details = "Everything is not fine"', + "expected_code": grpc.StatusCode.ABORTED, + "expected_details": "Everything is not fine", + "expect_error": False, + }, + { + "rpc_string": "status = , details = ", + "expected_code": None, + "expected_details": None, + "expect_error": True, + }, + { + "rpc_string": 'details = "Everything is fine"', + "expected_code": None, + "expected_details": None, + "expect_error": True, + }, + { + "rpc_string": "status = StatusCode.ABORTED", + "expected_code": None, + "expected_details": None, + "expect_error": True, + }, + { + "rpc_string": 'status = StatusCode.INVALID_STATUS_CODE_SAD, details = "Everything is fine"', + "expected_code": None, + "expected_details": None, + "expect_error": True, + }, + { + "rpc_string": ' status = StatusCode.CANCELLED , details = "Everything is not fine" ', + "expected_code": grpc.StatusCode.CANCELLED, + "expected_details": "Everything is not fine", + "expect_error": False, + }, + { + "rpc_string": "status=StatusCode.OK details='Everything is fine'", + "expected_code": None, + "expected_details": None, + "expect_error": True, + }, +] + + +@pytest.mark.parametrize("case", repr_test_cases) +def test_parse_rpc_repr_string(case): + try: + code, details = _parse_rpc_repr_string(case["rpc_string"], grpc) + assert not case[ + "expect_error" + ], f"Test case with repr string: {case['rpc_string']} expected error but got result" + assert ( + code == case["expected_code"] + ), f"Test case with repr string: {case['rpc_string']} expected code {case['expected_code']} but got {code}" + assert details == case["expected_details"], ( + f"Test case with repr string: {case['rpc_string']} expected details {case['expected_details']} but" + f"got {details}" + ) + except ValueError as e: + assert case[ + "expect_error" + ], f"Test case with repr string: {case['rpc_string']} did not expect error but got {e}" From b73738e4af3e450084b8a40e1d890511724d3d6d Mon Sep 17 00:00:00 2001 From: erikayasuda <153395705+erikayasuda@users.noreply.github.com> Date: Wed, 15 May 2024 15:03:19 -0400 Subject: [PATCH 053/104] feat(tracer): add `TracerFlareSubscriber` to enable the tracer flare (#9150) ## Overview This PR adds a remote config subscriber for the tracer flare. The tracer flare logic was already implemented in a previous PR, but was not wired up to actually react to the RC products yet. This PR is the last piece to enable the tracer flare for dd-trace-py. A tracer flare is a tar file containing tracer logs from (approximately) the last 5 minutes, as well as a JSON file of the current tracer configurations. This pair of files will be generated per tracer instance that is connected to the agent, and the tar containing all of these files will be sent to a Zendesk ticket. Users can trigger the tracer flare the same way they trigger agent flares. See Agent Flare documentation [here](https://docs.datadoghq.com/agent/troubleshooting/send_a_flare/?tab=agentv6v7). For details on the flare implementation, see https://github.com/DataDog/dd-trace-py/pull/8961 and https://github.com/DataDog/dd-trace-py/pull/8969. ## Risks ### `AGENT_CONFIG` doesn't get cleared very frequently Something I noticed when doing some E2E testing is that if you try to do consecutive tracer flare requests, this won't work because the `AGENT_CONFIG` is still retaining the state from the previous request. This means that there isn't an update/publish event that can get picked up on our end, so we can't trigger another flare for some amount of time (not sure what this duration is exactly). The current implementation depends on the publish event, so trying to trigger consecutive tracer flare requests will not work until the state gets cleared. ### `AGENT_CONFIG` and `AGENT_TASK` are not exclusive to tracer flare use Currently, the tracer is listening for changes to the `AGENT_CONFIG` and `AGENT_TASK` remote config products. This is originally intended for the **agent** flare, not the tracer flare, but at this time we are piggy-backing on this signal. For this reason, it's been flagged by other tracer teams that the format/contents of the products may not be guaranteed. In the case that we start to notice flares not being triggered/generated as expected, this may be a code fix to check for. The current expectation for the products is: `AGENT_CONFIG` ```json { "metadata":[ { "id":"flare-log-level.", "product_name":"AGENT_CONFIG", "sha256_hash":"xxx", "length":63, "tuf_version":3, "apply_state":2, "apply_error":"None" } ], "config":[ { "config":{ "log_level":"" }, "name":"flare-log-level." } ], "shared_data_counter":2 } ``` `AGENT_TASK` ```json { "metadata":[ { "id":"id1", "product_name":"AGENT_TASK", "sha256_hash":"xxx", "length":139, "tuf_version":4, "apply_state":2, "apply_error":"None" } ], "config":[ false, { "args":{ "case_id":"111", "hostname":"myhostname", "user_handle":"user.name@datadoghq.com" }, "task_type":"tracer_flare", "uuid":"yyyyyy" } ], "shared_data_counter":5 } ``` ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Brett Langdon --- .circleci/config.templ.yml | 5 +- ddtrace/internal/flare/__init__.py | 0 ddtrace/internal/flare/_subscribers.py | 78 ++++++++++ ddtrace/internal/{ => flare}/flare.py | 50 ++++--- ddtrace/internal/flare/handler.py | 90 +++++++++++ ddtrace/settings/config.py | 9 +- .../add-tracer-flare-65e275bca27631dd.yaml | 5 + tests/.suitespec.json | 2 +- tests/internal/test_tracer_flare.py | 141 ++++++++++++++++-- 9 files changed, 347 insertions(+), 33 deletions(-) create mode 100644 ddtrace/internal/flare/__init__.py create mode 100644 ddtrace/internal/flare/_subscribers.py rename ddtrace/internal/{ => flare}/flare.py (82%) create mode 100644 ddtrace/internal/flare/handler.py create mode 100644 releasenotes/notes/add-tracer-flare-65e275bca27631dd.yaml diff --git a/.circleci/config.templ.yml b/.circleci/config.templ.yml index 1cf66d7a209..5bb7d942be0 100644 --- a/.circleci/config.templ.yml +++ b/.circleci/config.templ.yml @@ -16,7 +16,7 @@ mongo_image: &mongo_image mongo:3.6@sha256:19c11a8f1064fd2bb713ef1270f79a742a184 httpbin_image: &httpbin_image kennethreitz/httpbin@sha256:2c7abc4803080c22928265744410173b6fea3b898872c01c5fd0f0f9df4a59fb vertica_image: &vertica_image vertica/vertica-ce:latest rabbitmq_image: &rabbitmq_image rabbitmq:3.7-alpine -testagent_image: &testagent_image ghcr.io/datadog/dd-apm-test-agent/ddapm-test-agent:v1.16.0 +testagent_image: &testagent_image ghcr.io/datadog/dd-apm-test-agent/ddapm-test-agent:v1.17.0 parameters: coverage: @@ -543,6 +543,9 @@ jobs: internal: <<: *contrib_job_small + docker: + - image: *ddtrace_dev_image + - *testagent steps: - run_test: pattern: "internal" diff --git a/ddtrace/internal/flare/__init__.py b/ddtrace/internal/flare/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ddtrace/internal/flare/_subscribers.py b/ddtrace/internal/flare/_subscribers.py new file mode 100644 index 00000000000..17703f6de01 --- /dev/null +++ b/ddtrace/internal/flare/_subscribers.py @@ -0,0 +1,78 @@ +from datetime import datetime +import os +from typing import Callable # noqa:F401 +from typing import Optional # noqa:F401 + +from ddtrace.internal.flare.flare import Flare +from ddtrace.internal.logger import get_logger +from ddtrace.internal.remoteconfig._connectors import PublisherSubscriberConnector # noqa:F401 +from ddtrace.internal.remoteconfig._subscribers import RemoteConfigSubscriber + + +log = get_logger(__name__) + +DEFAULT_STALE_FLARE_DURATION_MINS = 20 + + +class TracerFlareSubscriber(RemoteConfigSubscriber): + def __init__( + self, + data_connector: PublisherSubscriberConnector, + callback: Callable, + flare: Flare, + stale_flare_age: int = DEFAULT_STALE_FLARE_DURATION_MINS, + ): + super().__init__(data_connector, callback, "TracerFlareConfig") + self.current_request_start: Optional[datetime] = None + self.stale_tracer_flare_num_mins = stale_flare_age + self.flare = flare + + def has_stale_flare(self) -> bool: + if self.current_request_start: + curr = datetime.now() + flare_age = (curr - self.current_request_start).total_seconds() + stale_age = self.stale_tracer_flare_num_mins * 60 + return flare_age >= stale_age + return False + + def _get_data_from_connector_and_exec(self): + if self.has_stale_flare(): + log.info( + "Tracer flare request started at %s is stale, reverting " + "logger configurations and cleaning up resources now", + self.current_request_start, + ) + self.current_request_start = None + self._callback(self.flare, {}, True) + return + + data = self._data_connector.read() + metadata = data.get("metadata") + if not metadata: + log.debug("No metadata received from data connector") + return + + for md in metadata: + product_type = md.get("product_name") + if product_type == "AGENT_CONFIG": + # We will only process one tracer flare request at a time + if self.current_request_start is not None: + log.warning( + "There is already a tracer flare job started at %s. Skipping new request.", + str(self.current_request_start), + ) + return + self.current_request_start = datetime.now() + elif product_type == "AGENT_TASK": + # Possible edge case where we don't have an existing flare request + # In this case we won't have anything to send, so we log and do nothing + if self.current_request_start is None: + log.warning("There is no tracer flare job to complete. Skipping new request.") + return + self.current_request_start = None + else: + log.debug("Received unexpected product type for tracer flare: {}", product_type) + return + log.debug("[PID %d] %s _exec_callback: %s", os.getpid(), self, str(data)[:50]) + self._callback(self.flare, data) + return diff --git a/ddtrace/internal/flare.py b/ddtrace/internal/flare/flare.py similarity index 82% rename from ddtrace/internal/flare.py rename to ddtrace/internal/flare/flare.py index 7cf850e7656..8cde2f5a202 100644 --- a/ddtrace/internal/flare.py +++ b/ddtrace/internal/flare/flare.py @@ -12,7 +12,6 @@ from typing import Optional from typing import Tuple -from ddtrace import config from ddtrace._logger import _add_file_handler from ddtrace.internal.logger import get_logger from ddtrace.internal.utils.http import get_connection @@ -36,14 +35,26 @@ class FlareSendRequest: source: str = "tracer_python" +class TracerFlareSendError(Exception): + pass + + class Flare: - def __init__(self, timeout_sec: int = DEFAULT_TIMEOUT_SECONDS, flare_dir: str = TRACER_FLARE_DIRECTORY): + def __init__( + self, + trace_agent_url: str, + api_key: Optional[str] = None, + timeout_sec: int = DEFAULT_TIMEOUT_SECONDS, + flare_dir: str = TRACER_FLARE_DIRECTORY, + ): self.original_log_level: int = logging.NOTSET self.timeout: int = timeout_sec self.flare_dir: pathlib.Path = pathlib.Path(flare_dir) self.file_handler: Optional[RotatingFileHandler] = None + self.url: str = trace_agent_url + self._api_key: Optional[str] = api_key - def prepare(self, log_level: str): + def prepare(self, config: dict, log_level: str): """ Update configurations to start sending tracer logs to a file to be sent in a flare later. @@ -77,7 +88,7 @@ def prepare(self, log_level: str): ) # Create and add config file - self._generate_config_file(pid) + self._generate_config_file(config, pid) def send(self, flare_send_req: FlareSendRequest): """ @@ -96,35 +107,38 @@ def send(self, flare_send_req: FlareSendRequest): log.error("Failed to create %s file", lock_path) raise e try: - client = get_connection(config._trace_agent_url, timeout=self.timeout) + client = get_connection(self.url, timeout=self.timeout) headers, body = self._generate_payload(flare_send_req.__dict__) client.request("POST", TRACER_FLARE_ENDPOINT, body, headers) response = client.getresponse() if response.status == 200: log.info("Successfully sent the flare to Zendesk ticket %s", flare_send_req.case_id) else: - log.error( - "Tracer flare upload to Zendesk ticket %s failed with %s status code:(%s) %s", - flare_send_req.case_id, + msg = "Tracer flare upload responded with status code %s:(%s) %s" % ( response.status, response.reason, response.read().decode(), ) + raise TracerFlareSendError(msg) except Exception as e: - log.error("Failed to send tracer flare to Zendesk ticket %s", flare_send_req.case_id) + log.error("Failed to send tracer flare to Zendesk ticket %s: %s", flare_send_req.case_id, e) raise e finally: client.close() # Clean up files regardless of success/failure self.clean_up_files() - return - def _generate_config_file(self, pid: int): + def _generate_config_file(self, config: dict, pid: int): config_file = self.flare_dir / f"tracer_config_{pid}.json" try: with open(config_file, "w") as f: + # Redact API key if present + api_key = config.get("_dd_api_key") + if api_key: + config["_dd_api_key"] = "*" * (len(api_key) - 4) + api_key[-4:] + tracer_configs = { - "configs": config.__dict__, + "configs": config, } json.dump( tracer_configs, @@ -161,20 +175,20 @@ def _generate_payload(self, params: Dict[str, str]) -> Tuple[dict, bytes]: encoded_key = key.encode() encoded_value = value.encode() body.write(b"--" + boundary + newline) - body.write(b'Content-Disposition: form-data; name="{%s}"{%s}{%s}' % (encoded_key, newline, newline)) - body.write(b"{%s}{%s}" % (encoded_value, newline)) + body.write(b'Content-Disposition: form-data; name="%s"%s%s' % (encoded_key, newline, newline)) + body.write(b"%s%s" % (encoded_value, newline)) body.write(b"--" + boundary + newline) - body.write((b'Content-Disposition: form-data; name="flare_file"; filename="flare.tar"{%s}' % newline)) - body.write(b"Content-Type: application/octet-stream{%s}{%s}" % (newline, newline)) + body.write((b'Content-Disposition: form-data; name="flare_file"; filename="flare.tar"%s' % newline)) + body.write(b"Content-Type: application/octet-stream%s%s" % (newline, newline)) body.write(tar_stream.getvalue() + newline) body.write(b"--" + boundary + b"--") headers = { "Content-Type": b"multipart/form-data; boundary=%s" % boundary, "Content-Length": body.getbuffer().nbytes, } - if config._dd_api_key: - headers["DD-API-KEY"] = config._dd_api_key + if self._api_key: + headers["DD-API-KEY"] = self._api_key return headers, body.getvalue() def _get_valid_logger_level(self, flare_log_level: int) -> int: diff --git a/ddtrace/internal/flare/handler.py b/ddtrace/internal/flare/handler.py new file mode 100644 index 00000000000..9cb639ade8b --- /dev/null +++ b/ddtrace/internal/flare/handler.py @@ -0,0 +1,90 @@ +from typing import Any +from typing import Callable +from typing import List + +from ddtrace.internal.flare.flare import Flare +from ddtrace.internal.flare.flare import FlareSendRequest +from ddtrace.internal.logger import get_logger + + +log = get_logger(__name__) + + +def _tracerFlarePubSub(): + from ddtrace.internal.flare._subscribers import TracerFlareSubscriber + from ddtrace.internal.remoteconfig._connectors import PublisherSubscriberConnector + from ddtrace.internal.remoteconfig._publishers import RemoteConfigPublisher + from ddtrace.internal.remoteconfig._pubsub import PubSub + + class _TracerFlarePubSub(PubSub): + __publisher_class__ = RemoteConfigPublisher + __subscriber_class__ = TracerFlareSubscriber + __shared_data__ = PublisherSubscriberConnector() + + def __init__(self, callback: Callable, flare: Flare): + self._publisher = self.__publisher_class__(self.__shared_data__, None) + self._subscriber = self.__subscriber_class__(self.__shared_data__, callback, flare) + + return _TracerFlarePubSub + + +def _handle_tracer_flare(flare: Flare, data: dict, cleanup: bool = False): + if cleanup: + flare.revert_configs() + flare.clean_up_files() + return + + if "config" not in data: + log.warning("Unexpected tracer flare RC payload %r", data) + return + if len(data["config"]) == 0: + log.warning("Unexpected number of tracer flare RC payloads %r", data) + return + + product_type = data.get("metadata", [{}])[0].get("product_name") + configs = data.get("config", [{}]) + if product_type == "AGENT_CONFIG": + _prepare_tracer_flare(flare, configs) + elif product_type == "AGENT_TASK": + _generate_tracer_flare(flare, configs) + else: + log.warning("Received unexpected tracer flare product type: %s", product_type) + + +def _prepare_tracer_flare(flare: Flare, configs: List[dict]): + """ + Update configurations to start sending tracer logs to a file + to be sent in a flare later. + """ + for c in configs: + # AGENT_CONFIG is currently being used for multiple purposes + # We only want to prepare for a tracer flare if the config name + # starts with 'flare-log-level' + if not c.get("name", "").startswith("flare-log-level"): + continue + + flare_log_level = c.get("config", {}).get("log_level").upper() + flare.prepare(c, flare_log_level) + return + + +def _generate_tracer_flare(flare: Flare, configs: List[Any]): + """ + Revert tracer flare configurations back to original state + before sending the flare. + """ + for c in configs: + # AGENT_TASK is currently being used for multiple purposes + # We only want to generate the tracer flare if the task_type is + # 'tracer_flare' + if type(c) != dict or c.get("task_type") != "tracer_flare": + continue + args = c.get("args", {}) + flare_request = FlareSendRequest( + case_id=args.get("case_id"), hostname=args.get("hostname"), email=args.get("user_handle") + ) + + flare.revert_configs() + + flare.send(flare_request) + return diff --git a/ddtrace/settings/config.py b/ddtrace/settings/config.py index 10cc15a5828..148eb0aeabd 100644 --- a/ddtrace/settings/config.py +++ b/ddtrace/settings/config.py @@ -809,12 +809,17 @@ def _format_tags(self, tags: List[Union[str, Dict]]) -> Dict[str, str]: def enable_remote_configuration(self): # type: () -> None """Enable fetching configuration from Datadog.""" + from ddtrace.internal.flare.flare import Flare + from ddtrace.internal.flare.handler import _handle_tracer_flare + from ddtrace.internal.flare.handler import _tracerFlarePubSub from ddtrace.internal.remoteconfig.worker import remoteconfig_poller remoteconfig_pubsub = self._remoteconfigPubSub()(self._handle_remoteconfig) + flare = Flare(trace_agent_url=self._trace_agent_url, api_key=self._dd_api_key) + tracerflare_pubsub = _tracerFlarePubSub()(_handle_tracer_flare, flare) remoteconfig_poller.register("APM_TRACING", remoteconfig_pubsub) - remoteconfig_poller.register("AGENT_CONFIG", remoteconfig_pubsub) - remoteconfig_poller.register("AGENT_TASK", remoteconfig_pubsub) + remoteconfig_poller.register("AGENT_CONFIG", tracerflare_pubsub) + remoteconfig_poller.register("AGENT_TASK", tracerflare_pubsub) def _remove_invalid_rules(self, rc_rules: List) -> List: """Remove invalid sampling rules from the given list""" diff --git a/releasenotes/notes/add-tracer-flare-65e275bca27631dd.yaml b/releasenotes/notes/add-tracer-flare-65e275bca27631dd.yaml new file mode 100644 index 00000000000..9eac4658218 --- /dev/null +++ b/releasenotes/notes/add-tracer-flare-65e275bca27631dd.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + tracer: This introduces the tracer flare functionality. Currently the tracer flare includes the + tracer logs and tracer configurations. \ No newline at end of file diff --git a/tests/.suitespec.json b/tests/.suitespec.json index ca8921d3bb6..a01771321a8 100644 --- a/tests/.suitespec.json +++ b/tests/.suitespec.json @@ -101,7 +101,7 @@ "ddtrace/internal/_utils.*", "ddtrace/internal/constants.py", "ddtrace/internal/encoding.py", - "ddtrace/internal/flare.py", + "ddtrace/internal/flare/*", "ddtrace/internal/pack.h", "ddtrace/internal/pack_template.h", "ddtrace/internal/peer_service/*", diff --git a/tests/internal/test_tracer_flare.py b/tests/internal/test_tracer_flare.py index 560dcdc1ddd..c6af55a0a95 100644 --- a/tests/internal/test_tracer_flare.py +++ b/tests/internal/test_tracer_flare.py @@ -8,15 +8,19 @@ from unittest import mock import uuid -from ddtrace.internal.flare import TRACER_FLARE_DIRECTORY -from ddtrace.internal.flare import TRACER_FLARE_FILE_HANDLER_NAME -from ddtrace.internal.flare import Flare -from ddtrace.internal.flare import FlareSendRequest +from ddtrace.internal.flare._subscribers import TracerFlareSubscriber +from ddtrace.internal.flare.flare import TRACER_FLARE_DIRECTORY +from ddtrace.internal.flare.flare import TRACER_FLARE_FILE_HANDLER_NAME +from ddtrace.internal.flare.flare import Flare +from ddtrace.internal.flare.flare import FlareSendRequest +from ddtrace.internal.flare.handler import _handle_tracer_flare from ddtrace.internal.logger import get_logger +from ddtrace.internal.remoteconfig._connectors import PublisherSubscriberConnector from tests.utils import flaky DEBUG_LEVEL_INT = logging.DEBUG +TRACE_AGENT_URL = "http://localhost:9126" class TracerFlareTests(unittest.TestCase): @@ -24,10 +28,12 @@ class TracerFlareTests(unittest.TestCase): case_id="1111111", hostname="myhostname", email="user.name@datadoghq.com" ) + mock_config_dict = {} + def setUp(self): self.flare_uuid = uuid.uuid4() self.flare_dir = f"{TRACER_FLARE_DIRECTORY}-{self.flare_uuid}" - self.flare = Flare(flare_dir=pathlib.Path(self.flare_dir)) + self.flare = Flare(trace_agent_url=TRACE_AGENT_URL, flare_dir=pathlib.Path(self.flare_dir)) self.pid = os.getpid() self.flare_file_path = f"{self.flare_dir}/tracer_python_{self.pid}.log" self.config_file_path = f"{self.flare_dir}/tracer_config_{self.pid}.json" @@ -49,7 +55,7 @@ def test_single_process_success(self): """ ddlogger = get_logger("ddtrace") - self.flare.prepare("DEBUG") + self.flare.prepare(self.mock_config_dict, "DEBUG") file_handler = self._get_handler() valid_logger_level = self.flare._get_valid_logger_level(DEBUG_LEVEL_INT) @@ -74,8 +80,8 @@ def test_single_process_partial_failure(self): # Mock the partial failure with mock.patch("json.dump") as mock_json: - mock_json.side_effect = Exception("file issue happened") - self.flare.prepare("DEBUG") + mock_json.side_effect = Exception("this is an expected error") + self.flare.prepare(self.mock_config_dict, "DEBUG") file_handler = self._get_handler() assert file_handler is not None @@ -95,7 +101,7 @@ def test_multiple_process_success(self): num_processes = 3 def handle_agent_config(): - self.flare.prepare("DEBUG") + self.flare.prepare(self.mock_config_dict, "DEBUG") def handle_agent_task(): self.flare.send(self.mock_flare_send_request) @@ -128,7 +134,7 @@ def test_multiple_process_partial_failure(self): processes = [] def do_tracer_flare(prep_request, send_request): - self.flare.prepare(prep_request) + self.flare.prepare(self.mock_config_dict, prep_request) # Assert that only one process wrote its file successfully # We check for 2 files because it will generate a log file and a config file assert 2 == len(os.listdir(self.flare_dir)) @@ -151,7 +157,7 @@ def test_no_app_logs(self): file, just the tracer logs """ app_logger = Logger(name="my-app", level=DEBUG_LEVEL_INT) - self.flare.prepare("DEBUG") + self.flare.prepare(self.mock_config_dict, "DEBUG") app_log_line = "this is an app log" app_logger.debug(app_log_line) @@ -168,3 +174,116 @@ def test_no_app_logs(self): def confirm_cleanup(self): assert not self.flare.flare_dir.exists(), f"The directory {self.flare.flare_dir} still exists" assert self._get_handler() is None, "File handler was not removed" + + +class TracerFlareSubscriberTests(unittest.TestCase): + agent_config = [{"name": "flare-log-level", "config": {"log_level": "DEBUG"}}] + agent_task = [ + False, + { + "args": { + "case_id": "1111111", + "hostname": "myhostname", + "user_handle": "user.name@datadoghq.com", + }, + "task_type": "tracer_flare", + "uuid": "d53fc8a4-8820-47a2-aa7d-d565582feb81", + }, + ] + + def setUp(self): + self.tracer_flare_sub = TracerFlareSubscriber( + data_connector=PublisherSubscriberConnector(), + callback=_handle_tracer_flare, + flare=Flare(trace_agent_url=TRACE_AGENT_URL), + ) + + def generate_agent_config(self): + with mock.patch( + "ddtrace.internal.remoteconfig._connectors.PublisherSubscriberConnector.read" + ) as mock_pubsub_conn: + mock_pubsub_conn.return_value = { + "metadata": [{"product_name": "AGENT_CONFIG"}], + "config": self.agent_config, + } + self.tracer_flare_sub._get_data_from_connector_and_exec() + + def generate_agent_task(self): + with mock.patch( + "ddtrace.internal.remoteconfig._connectors.PublisherSubscriberConnector.read" + ) as mock_pubsub_conn: + mock_pubsub_conn.return_value = { + "metadata": [{"product_name": "AGENT_TASK"}], + "config": self.agent_task, + } + self.tracer_flare_sub._get_data_from_connector_and_exec() + + def test_process_flare_request_success(self): + """ + Ensure a full successful tracer flare process + """ + assert self.tracer_flare_sub.stale_tracer_flare_num_mins == 20 + # Generate an AGENT_CONFIG product to trigger a request + with mock.patch("ddtrace.internal.flare.flare.Flare.prepare") as mock_flare_prep: + self.generate_agent_config() + mock_flare_prep.assert_called_once() + + assert ( + self.tracer_flare_sub.current_request_start is not None + ), "current_request_start should be a non-None value after request is received" + + # Generate an AGENT_TASK product to complete the request + with mock.patch("ddtrace.internal.flare.flare.Flare.send") as mock_flare_send: + self.generate_agent_task() + mock_flare_send.assert_called_once() + + # Timestamp cleared after request completed + assert ( + self.tracer_flare_sub.current_request_start is None + ), "current_request_start timestamp should have been reset after request was completed" + + def test_detect_stale_flare(self): + """ + Ensure we clean up and revert configurations if a tracer + flare job has gone stale + """ + # Set this to 0 so all requests are stale + self.tracer_flare_sub.stale_tracer_flare_num_mins = 0 + + # Generate an AGENT_CONFIG product to trigger a request + with mock.patch("ddtrace.internal.flare.flare.Flare.prepare") as mock_flare_prep: + self.generate_agent_config() + mock_flare_prep.assert_called_once() + + assert self.tracer_flare_sub.current_request_start is not None + + # Setting this to 0 minutes so all jobs are considered stale + assert self.tracer_flare_sub.has_stale_flare() + + self.generate_agent_config() + + assert self.tracer_flare_sub.current_request_start is None, "current_request_start should have been reset" + + def test_no_overlapping_requests(self): + """ + If a new tracer flare request is generated while processing + a pre-existing request, we will continue processing the current + one while disregarding the new request(s) + """ + # Generate initial AGENT_CONFIG product to trigger a request + with mock.patch("ddtrace.internal.flare.flare.Flare.prepare") as mock_flare_prep: + self.generate_agent_config() + mock_flare_prep.assert_called_once() + + original_request_start = self.tracer_flare_sub.current_request_start + assert original_request_start is not None + + # Generate another AGENT_CONFIG product to trigger a request + # This should not be processed + with mock.patch("ddtrace.internal.flare.flare.Flare.prepare") as mock_flare_prep: + self.generate_agent_config() + mock_flare_prep.assert_not_called() + + assert ( + self.tracer_flare_sub.current_request_start == original_request_start + ), "Original request should not have been updated with newer request start time" From d4d4abe1be6ccac4ce04d92e7c6149e7385c59fa Mon Sep 17 00:00:00 2001 From: Juanjo Alvarez Martinez Date: Thu, 16 May 2024 11:58:39 +0200 Subject: [PATCH 054/104] fix(asm): fix string aspects potentially being called on module aspects (#9279) ## Description In some corner cases, modules with a function like `split` that are also string methods, could call the string method instead of the module function, which could even cause a segmentation fault with the to-be-fixed-on-another-PR `Initializer`. This also changes the `aspects_specs` from being creating on each instantiation of the `AstVisitor` to being a module constant. This was done mostly for making it easier to test but as a nice side-effect it should improve performance. ## Checklist - [X] Change(s) are motivated and described in the PR description - [X] Testing strategy is described if automated tests are not included in the PR - [X] Risks are described (performance impact, potential for breakage, maintainability) - [X] Change is maintainable (easy to change, telemetry, documentation) - [X] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [X] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [X] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [X] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Signed-off-by: Juanjo Alvarez Co-authored-by: Alberto Vara --- ddtrace/appsec/_iast/_ast/visitor.py | 260 +++++++++--------- .../aspects/test_ospath_aspects_fixtures.py | 32 +++ 2 files changed, 168 insertions(+), 124 deletions(-) diff --git a/ddtrace/appsec/_iast/_ast/visitor.py b/ddtrace/appsec/_iast/_ast/visitor.py index 2da7bbdcda2..086c14ce80c 100644 --- a/ddtrace/appsec/_iast/_ast/visitor.py +++ b/ddtrace/appsec/_iast/_ast/visitor.py @@ -34,113 +34,114 @@ def _mark_avoid_convert_recursively(node): _mark_avoid_convert_recursively(child) +_ASPECTS_SPEC = { + "definitions_module": "ddtrace.appsec._iast._taint_tracking.aspects", + "alias_module": "ddtrace_aspects", + "functions": { + "str": "ddtrace_aspects.str_aspect", + "bytes": "ddtrace_aspects.bytes_aspect", + "bytearray": "ddtrace_aspects.bytearray_aspect", + "ddtrace_iast_flask_patch": "ddtrace_aspects.empty_func", # To avoid recursion + }, + "stringalike_methods": { + "decode": "ddtrace_aspects.decode_aspect", + "join": "ddtrace_aspects.join_aspect", + "encode": "ddtrace_aspects.encode_aspect", + "extend": "ddtrace_aspects.bytearray_extend_aspect", + "upper": "ddtrace_aspects.upper_aspect", + "lower": "ddtrace_aspects.lower_aspect", + "replace": "ddtrace_aspects.replace_aspect", + "swapcase": "ddtrace_aspects.swapcase_aspect", + "title": "ddtrace_aspects.title_aspect", + "capitalize": "ddtrace_aspects.capitalize_aspect", + "casefold": "ddtrace_aspects.casefold_aspect", + "translate": "ddtrace_aspects.translate_aspect", + "format": "ddtrace_aspects.format_aspect", + "format_map": "ddtrace_aspects.format_map_aspect", + "zfill": "ddtrace_aspects.zfill_aspect", + "ljust": "ddtrace_aspects.ljust_aspect", + "split": "ddtrace_aspects.split_aspect", + "rsplit": "ddtrace_aspects.rsplit_aspect", + "splitlines": "ddtrace_aspects.splitlines_aspect", + }, + # Replacement function for indexes and ranges + "slices": { + "index": "ddtrace_aspects.index_aspect", + "slice": "ddtrace_aspects.slice_aspect", + }, + # Replacement functions for modules + "module_functions": { + "os.path": { + "basename": "ddtrace_aspects._aspect_ospathbasename", + "dirname": "ddtrace_aspects._aspect_ospathdirname", + "join": "ddtrace_aspects._aspect_ospathjoin", + "normcase": "ddtrace_aspects._aspect_ospathnormcase", + "split": "ddtrace_aspects._aspect_ospathsplit", + "splitext": "ddtrace_aspects._aspect_ospathsplitext", + } + }, + "operators": { + ast.Add: "ddtrace_aspects.add_aspect", + "FORMAT_VALUE": "ddtrace_aspects.format_value_aspect", + ast.Mod: "ddtrace_aspects.modulo_aspect", + "BUILD_STRING": "ddtrace_aspects.build_string_aspect", + }, + "excluded_from_patching": { + # Key: module being patched + # Value: dict with more info + "django.utils.formats": { + # Key: called functions that won't be patched. E.g.: for this module + # not a single call for format on any function will be patched. + # + # Value: function definitions. E.g.: we won't patch any Call node inside + # the iter_format_modules(). If we, for example, had 'foo': ('bar', 'baz') + # it would mean that we wouldn't patch any call to foo() done inside the + # bar() or baz() function definitions. + "format": ("",), + "": ("iter_format_modules",), + }, + "django.utils.log": { + "": ("",), + }, + "django.utils.html": {"": ("format_html", "format_html_join")}, + }, + # This is a set since all functions will be replaced by taint_sink_functions + "taint_sinks": { + "weak_randomness": DEFAULT_WEAK_RANDOMNESS_FUNCTIONS, + "path_traversal": DEFAULT_PATH_TRAVERSAL_FUNCTIONS, + "other": { + "load", + "run", + "path", + "exit", + "sleep", + "socket", + }, + # These explicitly WON'T be replaced by taint_sink_function: + "disabled": { + "__new__", + "__init__", + "__dir__", + "__repr__", + "super", + }, + }, +} + + +if sys.version_info >= (3, 12): + _ASPECTS_SPEC["module_functions"]["os.path"]["splitroot"] = "ddtrace_aspects._aspect_ospathsplitroot" + +if sys.version_info >= (3, 12) or os.name == "nt": + _ASPECTS_SPEC["module_functions"]["os.path"]["splitdrive"] = "ddtrace_aspects._aspect_ospathsplitdrive" # type: ignore[index] + + class AstVisitor(ast.NodeTransformer): def __init__( self, filename="", module_name="", ): - # Offset caused by inserted lines. Will be adjusted in visit_Generic - self._aspects_spec = { - "definitions_module": "ddtrace.appsec._iast._taint_tracking.aspects", - "alias_module": "ddtrace_aspects", - "functions": { - "str": "ddtrace_aspects.str_aspect", - "bytes": "ddtrace_aspects.bytes_aspect", - "bytearray": "ddtrace_aspects.bytearray_aspect", - "ddtrace_iast_flask_patch": "ddtrace_aspects.empty_func", # To avoid recursion - }, - "stringalike_methods": { - "decode": "ddtrace_aspects.decode_aspect", - "join": "ddtrace_aspects.join_aspect", - "encode": "ddtrace_aspects.encode_aspect", - "extend": "ddtrace_aspects.bytearray_extend_aspect", - "upper": "ddtrace_aspects.upper_aspect", - "lower": "ddtrace_aspects.lower_aspect", - "replace": "ddtrace_aspects.replace_aspect", - "swapcase": "ddtrace_aspects.swapcase_aspect", - "title": "ddtrace_aspects.title_aspect", - "capitalize": "ddtrace_aspects.capitalize_aspect", - "casefold": "ddtrace_aspects.casefold_aspect", - "translate": "ddtrace_aspects.translate_aspect", - "format": "ddtrace_aspects.format_aspect", - "format_map": "ddtrace_aspects.format_map_aspect", - "zfill": "ddtrace_aspects.zfill_aspect", - "ljust": "ddtrace_aspects.ljust_aspect", - "split": "ddtrace_aspects.split_aspect", - "rsplit": "ddtrace_aspects.rsplit_aspect", - "splitlines": "ddtrace_aspects.splitlines_aspect", - }, - # Replacement function for indexes and ranges - "slices": { - "index": "ddtrace_aspects.index_aspect", - "slice": "ddtrace_aspects.slice_aspect", - }, - # Replacement functions for modules - "module_functions": { - "os.path": { - "basename": "ddtrace_aspects._aspect_ospathbasename", - "dirname": "ddtrace_aspects._aspect_ospathdirname", - "join": "ddtrace_aspects._aspect_ospathjoin", - "normcase": "ddtrace_aspects._aspect_ospathnormcase", - "split": "ddtrace_aspects._aspect_ospathsplit", - "splitext": "ddtrace_aspects._aspect_ospathsplitext", - } - }, - "operators": { - ast.Add: "ddtrace_aspects.add_aspect", - "FORMAT_VALUE": "ddtrace_aspects.format_value_aspect", - ast.Mod: "ddtrace_aspects.modulo_aspect", - "BUILD_STRING": "ddtrace_aspects.build_string_aspect", - }, - "excluded_from_patching": { - # Key: module being patched - # Value: dict with more info - "django.utils.formats": { - # Key: called functions that won't be patched. E.g.: for this module - # not a single call for format on any function will be patched. - # - # Value: function definitions. E.g.: we won't patch any Call node inside - # the iter_format_modules(). If we, for example, had 'foo': ('bar', 'baz') - # it would mean that we wouldn't patch any call to foo() done inside the - # bar() or baz() function definitions. - "format": ("",), - "": ("iter_format_modules",), - }, - "django.utils.log": { - "": ("",), - }, - "django.utils.html": {"": ("format_html", "format_html_join")}, - }, - # This is a set since all functions will be replaced by taint_sink_functions - "taint_sinks": { - "weak_randomness": DEFAULT_WEAK_RANDOMNESS_FUNCTIONS, - "path_traversal": DEFAULT_PATH_TRAVERSAL_FUNCTIONS, - "other": { - "load", - "run", - "path", - "exit", - "sleep", - "socket", - }, - # These explicitly WON'T be replaced by taint_sink_function: - "disabled": { - "__new__", - "__init__", - "__dir__", - "__repr__", - "super", - }, - }, - } - - if sys.version_info >= (3, 12): - self._aspects_spec["module_functions"]["os.path"]["splitroot"] = "ddtrace_aspects._aspect_ospathsplitroot" - - if sys.version_info >= (3, 12) or os.name == "nt": - self._aspects_spec["module_functions"]["os.path"]["splitdrive"] = "ddtrace_aspects._aspect_ospathsplitdrive" - self._sinkpoints_spec = { "definitions_module": "ddtrace.appsec._iast.taint_sinks", "alias_module": "ddtrace_taint_sinks", @@ -154,23 +155,23 @@ def __init__( self.filename = filename self.module_name = module_name - self._aspect_index = self._aspects_spec["slices"]["index"] - self._aspect_slice = self._aspects_spec["slices"]["slice"] - self._aspect_functions = self._aspects_spec["functions"] - self._aspect_operators = self._aspects_spec["operators"] - self._aspect_methods = self._aspects_spec["stringalike_methods"] - self._aspect_modules = self._aspects_spec["module_functions"] - self._aspect_format_value = self._aspects_spec["operators"]["FORMAT_VALUE"] - self._aspect_build_string = self._aspects_spec["operators"]["BUILD_STRING"] - self.excluded_functions = self._aspects_spec["excluded_from_patching"].get(self.module_name, {}) + self._aspect_index = _ASPECTS_SPEC["slices"]["index"] + self._aspect_slice = _ASPECTS_SPEC["slices"]["slice"] + self._aspect_functions = _ASPECTS_SPEC["functions"] + self._aspect_operators = _ASPECTS_SPEC["operators"] + self._aspect_methods = _ASPECTS_SPEC["stringalike_methods"] + self._aspect_modules = _ASPECTS_SPEC["module_functions"] + self._aspect_format_value = _ASPECTS_SPEC["operators"]["FORMAT_VALUE"] + self._aspect_build_string = _ASPECTS_SPEC["operators"]["BUILD_STRING"] + self.excluded_functions = _ASPECTS_SPEC["excluded_from_patching"].get(self.module_name, {}) # Sink points self._taint_sink_replace_any = self._merge_taint_sinks( - self._aspects_spec["taint_sinks"]["other"], - self._aspects_spec["taint_sinks"]["weak_randomness"], - *[functions for module, functions in self._aspects_spec["taint_sinks"]["path_traversal"].items()], + _ASPECTS_SPEC["taint_sinks"]["other"], + _ASPECTS_SPEC["taint_sinks"]["weak_randomness"], + *[functions for module, functions in _ASPECTS_SPEC["taint_sinks"]["path_traversal"].items()], ) - self._taint_sink_replace_disabled = self._aspects_spec["taint_sinks"]["disabled"] + self._taint_sink_replace_disabled = _ASPECTS_SPEC["taint_sinks"]["disabled"] self.dont_patch_these_functionsdefs = set() for _, v in self.excluded_functions.items(): @@ -397,7 +398,7 @@ def visit_Module(self, module_node: ast.Module) -> Any: """ insert_position = self.find_insert_position(module_node) - definitions_module = self._aspects_spec["definitions_module"] + definitions_module = _ASPECTS_SPEC["definitions_module"] replacements_import = self._node( ast.Import, module_node, @@ -406,7 +407,7 @@ def visit_Module(self, module_node: ast.Module) -> Any: lineno=1, col_offset=0, name=definitions_module, - asname=self._aspects_spec["alias_module"], + asname=_ASPECTS_SPEC["alias_module"], ) ], ) @@ -517,6 +518,8 @@ def visit_Call(self, call_node: ast.Call) -> Any: func_value_attr = getattr(func_value, "attr", None) if func_value else None func_attr = getattr(func_member, "attr", None) aspect = None + is_module_symbol = False + if func_value_value_id or func_attr: if func_value_value_id and func_value_attr: # e.g. "os.path" or "one.two.three.whatever" (all dotted previous tokens with be in the id) @@ -529,14 +532,23 @@ def visit_Call(self, call_node: ast.Call) -> Any: if key: module_dict = self._aspect_modules.get(key, None) - aspect = module_dict.get(func_attr, None) if module_dict else None - if aspect: - # Create a new Name node for the replacement and set it as node.func - call_node.func = self._attr_node(call_node, aspect) - self.ast_modified = call_modified = True - - if not aspect: - # Not a module symbol, check if it's a known method + # using "is not None" here because we want to mark is_module_symbol even if the dict is + # empty (e.g. we don't have an aspect for this specific function but we plan to, or we create + # empty dicts for some modules to avoid checking for string methods on their symbols) + if module_dict is not None: + aspect = module_dict.get(func_attr, None) + # since this is a module symbol, even if we don't have an aspect for this specific function, + # set this, so we don't try to replace as a string method + is_module_symbol = True + if aspect: + # Create a new Name node for the replacement and set it as node.func + call_node.func = self._attr_node(call_node, aspect) + self.ast_modified = call_modified = True + else: + aspect = None + + if (not is_module_symbol) and (not aspect): + # Not a module symbol, check if it's a known string method aspect = self._aspect_methods.get(method_name) if aspect: diff --git a/tests/appsec/iast/aspects/test_ospath_aspects_fixtures.py b/tests/appsec/iast/aspects/test_ospath_aspects_fixtures.py index 136f3d0fcc4..f36bf6243d6 100644 --- a/tests/appsec/iast/aspects/test_ospath_aspects_fixtures.py +++ b/tests/appsec/iast/aspects/test_ospath_aspects_fixtures.py @@ -2,6 +2,7 @@ import os import sys +import mock import pytest from ddtrace.appsec._constants import IAST @@ -69,6 +70,37 @@ def test_ospathsplit_tainted(): ] +def test_ospathsplit_noaspect_dont_call_string_aspect(): + global mod + + with mock.patch("ddtrace.appsec._iast._taint_tracking.aspects.split_aspect") as str_split_aspect: + with mock.patch("ddtrace.appsec._iast._taint_tracking.aspects._aspect_ospathsplit") as os_split_aspect: + import ddtrace.appsec._iast._ast.visitor as visitor + + old_aspect = visitor._ASPECTS_SPEC["module_functions"]["os.path"]["split"] + try: + del visitor._ASPECTS_SPEC["module_functions"]["os.path"]["split"] + del mod + del sys.modules["tests.appsec.iast.fixtures.aspects.module_functions"] + mod = _iast_patched_module("tests.appsec.iast.fixtures.aspects.module_functions") + string_input = taint_pyobject( + pyobject="/foo/bar", + source_name="first_element", + source_value="/foo/bar", + source_origin=OriginType.PARAMETER, + ) + result = mod.do_os_path_split(string_input) + assert result == ("/foo", "bar") + assert get_tainted_ranges(result[0]) == [] + assert get_tainted_ranges(result[1]) == [] + assert not str_split_aspect.called + assert not os_split_aspect.called + finally: + visitor._ASPECTS_SPEC["module_functions"]["os.path"]["split"] = old_aspect + del sys.modules["tests.appsec.iast.fixtures.aspects.module_functions"] + mod = _iast_patched_module("tests.appsec.iast.fixtures.aspects.module_functions") + + def test_ospathsplitext_tainted(): string_input = taint_pyobject( pyobject="/foo/bar.txt", From d0811139d2d101043f7fd20b4c97a9b83a806e22 Mon Sep 17 00:00:00 2001 From: Federico Mon Date: Thu, 16 May 2024 13:36:44 +0200 Subject: [PATCH 055/104] perf(iast): improve performance for patching decision (#9265) IAST: Performance optimization. Chages logic for IAST allowlist and denylist, now allowlist has precedence over denylist. ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Alberto Vara --- ddtrace/appsec/_iast/_ast/ast_patching.py | 17 +++++++++++++---- tests/appsec/iast/test_env_var.py | 7 ++----- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/ddtrace/appsec/_iast/_ast/ast_patching.py b/ddtrace/appsec/_iast/_ast/ast_patching.py index e27f2181644..721fbf9a7bd 100644 --- a/ddtrace/appsec/_iast/_ast/ast_patching.py +++ b/ddtrace/appsec/_iast/_ast/ast_patching.py @@ -31,6 +31,9 @@ "api_pb2", # Patching crashes with these auto-generated modules, propagation is not needed "api_pb2_grpc", # ditto "unittest.mock", + "pytest", # Testing framework + "freezegun", # Testing utilities for time manipulation + "sklearn", # Machine learning library ) @@ -86,10 +89,16 @@ def _should_iast_patch(module_name: Text) -> bool: select if module_name should be patch from the longuest prefix that match in allow or deny list. if a prefix is in both list, deny is selected. """ - max_allow = max((len(prefix) for prefix in IAST_ALLOWLIST if module_name.startswith(prefix)), default=-1) - max_deny = max((len(prefix) for prefix in IAST_DENYLIST if module_name.startswith(prefix)), default=-1) - diff = max_allow - max_deny - return diff > 0 or (diff == 0 and not _in_python_stdlib_or_third_party(module_name)) + # TODO: A better solution would be to migrate the original algorithm to C++: + # max_allow = max((len(prefix) for prefix in IAST_ALLOWLIST if module_name.startswith(prefix)), default=-1) + # max_deny = max((len(prefix) for prefix in IAST_DENYLIST if module_name.startswith(prefix)), default=-1) + # diff = max_allow - max_deny + # return diff > 0 or (diff == 0 and not _in_python_stdlib_or_third_party(module_name)) + if module_name.startswith(IAST_ALLOWLIST): + return True + if module_name.startswith(IAST_DENYLIST): + return False + return not _in_python_stdlib_or_third_party(module_name) def visit_ast( diff --git a/tests/appsec/iast/test_env_var.py b/tests/appsec/iast/test_env_var.py index 8d04ee09119..12008077beb 100644 --- a/tests/appsec/iast/test_env_var.py +++ b/tests/appsec/iast/test_env_var.py @@ -95,7 +95,7 @@ def test_A_env_var_iast_modules_to_patch(capfd): gc.collect() os.environ[IAST.PATCH_MODULES] = IAST.SEP_MODULES.join( - ["please_patch", "also.that", "ddtrace", "please_patch.do_not.but_yes"] + ["ddtrace.allowed", "please_patch", "also.that", "please_patch.do_not.but_yes"] ) os.environ[IAST.DENY_MODULES] = IAST.SEP_MODULES.join(["please_patch.do_not", "also.that.but.not.that"]) import ddtrace.appsec._iast._ast.ast_patching as ap @@ -112,6 +112,7 @@ def test_A_env_var_iast_modules_to_patch(capfd): "also.that.but.not", "tests.appsec.iast", "tests.appsec.iast.sub", + "ddtrace.allowed", ]: assert ap._should_iast_patch(module_name), module_name @@ -120,9 +121,5 @@ def test_A_env_var_iast_modules_to_patch(capfd): "ddtrace.sub", "hypothesis", "pytest", - "please_patch.do_not", - "please_patch.do_not.any", - "also.that.but.not.that", - "also.that.but.not.that.never", ]: assert not ap._should_iast_patch(module_name), module_name From de69597a925888ab00b99e8d84aa948c18d8bbf4 Mon Sep 17 00:00:00 2001 From: Juanjo Alvarez Martinez Date: Thu, 16 May 2024 14:45:38 +0200 Subject: [PATCH 056/104] fix(asm): initializer rework and fix for segmentation faults (#9284) ## Description Fix several segmentation faults (3 confirmed, probably more). This is done by a small rework of the Initializer which includes: - Moving `active_map_addreses` from `unordered_set` to `unordered_map` so we can iterate and change it without making a copy (making a copy was actually causing one of the segmentation faults because we were setting the `tx_map` pointer to `nullptr`, but on the copy, not the original, so `get_tainting_map` would return `not nullptr` but still garbage since it was deleted). - Convert the `TaintRangeMapType` pointer to a `shared_ptr`. This fixed another segfault and makes the codebase more robust. - Make `ThreadContextCache` not use a dangerously magical number-pointer but just a reference to the active `tx_map` so we only need to check if it's `nullptr` to see if we are in context (which is what functions did anyway). - Removed `destroy_context`. Not needed anymore, `reset_context` will do the same in a safer way. ## Checklist - [X] Change(s) are motivated and described in the PR description - [X] Testing strategy is described if automated tests are not included in the PR - [X] Risks are described (performance impact, potential for breakage, maintainability) - [X] Change is maintainable (easy to change, telemetry, documentation) - [X] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [X] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [X] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [X] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Signed-off-by: Juanjo Alvarez Co-authored-by: Federico Mon --- .../_taint_tracking/Aspects/AspectFormat.cpp | 2 +- .../_taint_tracking/Aspects/AspectIndex.cpp | 2 +- .../_taint_tracking/Aspects/AspectIndex.h | 2 +- .../_taint_tracking/Aspects/AspectJoin.cpp | 8 ++-- .../Aspects/AspectOperatorAdd.cpp | 7 ++- .../_taint_tracking/Aspects/AspectSplit.cpp | 12 ++--- .../_taint_tracking/Aspects/AspectsOsPath.cpp | 18 ++++---- .../_iast/_taint_tracking/Aspects/Helpers.cpp | 23 ++++++---- .../_iast/_taint_tracking/Aspects/Helpers.h | 2 +- .../Initializer/Initializer.cpp | 45 +++++++------------ .../_taint_tracking/Initializer/Initializer.h | 18 +++----- .../TaintTracking/TaintRange.cpp | 20 ++++----- .../TaintTracking/TaintRange.h | 11 +++-- .../_taint_tracking/TaintedOps/TaintedOps.cpp | 4 +- .../_taint_tracking/TaintedOps/TaintedOps.h | 2 +- .../appsec/_iast/_taint_tracking/__init__.py | 2 - .../iast-segfaults-fix-d25c5c132dd8482e.yaml | 4 ++ tests/appsec/iast/aspects/test_add_aspect.py | 8 ++-- .../aspects/test_bytearray_extend_aspect.py | 4 +- .../aspects/test_format_aspect_fixtures.py | 4 +- .../aspects/test_index_aspect_fixtures.py | 4 +- .../iast/aspects/test_join_aspect_fixtures.py | 8 ++-- .../aspects/test_ospath_aspects_fixtures.py | 4 +- .../aspects/test_slice_aspect_fixtures.py | 4 +- .../appsec/iast/aspects/test_split_aspect.py | 4 +- .../taint_tracking/test_taint_tracking.py | 8 ++-- 26 files changed, 114 insertions(+), 116 deletions(-) create mode 100644 releasenotes/notes/iast-segfaults-fix-d25c5c132dd8482e.yaml diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectFormat.cpp b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectFormat.cpp index 9a3abf883e8..33295dec186 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectFormat.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectFormat.cpp @@ -17,7 +17,7 @@ api_format_aspect(StrType& candidate_text, const py::args& args, const py::kwargs& kwargs) { - auto tx_map = initializer->get_tainting_map(); + const auto tx_map = initializer->get_tainting_map(); if (not tx_map or tx_map->empty()) { return py::getattr(candidate_text, "format")(*args, **kwargs); diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectIndex.cpp b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectIndex.cpp index 026d8ed0355..ec77852e9dc 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectIndex.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectIndex.cpp @@ -10,7 +10,7 @@ * @return PyObject* */ PyObject* -index_aspect(PyObject* result_o, PyObject* candidate_text, PyObject* idx, TaintRangeMapType* tx_taint_map) +index_aspect(PyObject* result_o, PyObject* candidate_text, PyObject* idx, const TaintRangeMapTypePtr& tx_taint_map) { auto idx_long = PyLong_AsLong(idx); bool ranges_error; diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectIndex.h b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectIndex.h index d22b52b3ec2..16ad8b487c9 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectIndex.h +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectIndex.h @@ -3,6 +3,6 @@ #include "TaintedOps/TaintedOps.h" PyObject* -index_aspect(PyObject* result_o, PyObject* candidate_text, PyObject* idx, TaintRangeMapType* tx_taint_map); +index_aspect(PyObject* result_o, PyObject* candidate_text, PyObject* idx, const TaintRangeMapTypePtr& tx_taint_map); PyObject* api_index_aspect(PyObject* self, PyObject* const* args, Py_ssize_t nargs); diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectJoin.cpp b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectJoin.cpp index c98b740253f..ad3f843e3d7 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectJoin.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectJoin.cpp @@ -5,7 +5,7 @@ aspect_join_str(PyObject* sep, PyObject* result, PyObject* iterable_str, size_t len_iterable, - TaintRangeMapType* tx_taint_map) + const TaintRangeMapTypePtr& tx_taint_map) { // This is the special case for unicode str and unicode iterable_str. // The iterable elements string will be split into 1 char-length strings. @@ -58,7 +58,7 @@ aspect_join_str(PyObject* sep, } PyObject* -aspect_join(PyObject* sep, PyObject* result, PyObject* iterable_elements, TaintRangeMapType* tx_taint_map) +aspect_join(PyObject* sep, PyObject* result, PyObject* iterable_elements, const TaintRangeMapTypePtr& tx_taint_map) { const size_t& len_sep = get_pyobject_size(sep); @@ -149,8 +149,6 @@ api_join_aspect(PyObject* self, PyObject* const* args, Py_ssize_t nargs) PyObject* arg0 = args[1]; bool decref_arg0 = false; - auto ctx_map = initializer->get_tainting_map(); - if (PyIter_Check(arg0) or PySet_Check(arg0) or PyFrozenSet_Check(arg0)) { PyObject* iterator = PyObject_GetIter(arg0); @@ -179,6 +177,8 @@ api_join_aspect(PyObject* self, PyObject* const* args, Py_ssize_t nargs) result = result_ptr.ptr(); Py_INCREF(result); } + + const auto ctx_map = initializer->get_tainting_map(); if (not ctx_map or ctx_map->empty() or get_pyobject_size(result) == 0) { // Empty result cannot have taint ranges if (decref_arg0) { diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectOperatorAdd.cpp b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectOperatorAdd.cpp index 376f741bd7d..af2deef7d0f 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectOperatorAdd.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectOperatorAdd.cpp @@ -11,7 +11,10 @@ * @return A new result object with the taint information. */ PyObject* -add_aspect(PyObject* result_o, PyObject* candidate_text, PyObject* text_to_add, TaintRangeMapType* tx_taint_map) +add_aspect(PyObject* result_o, + PyObject* candidate_text, + PyObject* text_to_add, + const TaintRangeMapTypePtr& tx_taint_map) { size_t len_candidate_text{ get_pyobject_size(candidate_text) }; size_t len_text_to_add{ get_pyobject_size(text_to_add) }; @@ -95,7 +98,7 @@ api_add_aspect(PyObject* self, PyObject* const* args, Py_ssize_t nargs) return result_o; } - TaintRangeMapType* tx_map = initializer->get_tainting_map(); + const auto tx_map = initializer->get_tainting_map(); if (not tx_map or tx_map->empty()) { return result_o; } diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectSplit.cpp b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectSplit.cpp index d4aecd740d8..aae847e19b2 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectSplit.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectSplit.cpp @@ -8,8 +8,8 @@ api_split_text(const StrType& text, const optional& separator, const op auto split = text.attr("split"); auto split_result = split(separator, maxsplit); - TaintRangeMapType* tx_map = initializer->get_tainting_map(); - if (not tx_map) { + const auto tx_map = initializer->get_tainting_map(); + if (not tx_map or tx_map->empty()) { return split_result; } @@ -27,8 +27,8 @@ api_rsplit_text(const StrType& text, const optional& separator, const o { auto rsplit = text.attr("rsplit"); auto split_result = rsplit(separator, maxsplit); - TaintRangeMapType* tx_map = initializer->get_tainting_map(); - if (not tx_map) { + const auto tx_map = initializer->get_tainting_map(); + if (not tx_map or tx_map->empty()) { return split_result; } @@ -45,8 +45,8 @@ api_splitlines_text(const StrType& text, bool keepends) { auto splitlines = text.attr("splitlines"); auto split_result = splitlines(keepends); - TaintRangeMapType* tx_map = initializer->get_tainting_map(); - if (not tx_map) { + const auto tx_map = initializer->get_tainting_map(); + if (not tx_map or tx_map->empty()) { return split_result; } diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectsOsPath.cpp b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectsOsPath.cpp index 213bf53b971..db40fb0301a 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectsOsPath.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectsOsPath.cpp @@ -18,7 +18,7 @@ api_ospathjoin_aspect(StrType& first_part, const py::args& args) auto join = ospath.attr("join"); auto joined = join(first_part, *args); - auto tx_map = initializer->get_tainting_map(); + const auto tx_map = initializer->get_tainting_map(); if (not tx_map or tx_map->empty()) { return joined; } @@ -107,8 +107,8 @@ api_ospathbasename_aspect(const StrType& path) auto basename = ospath.attr("basename"); auto basename_result = basename(path); - auto tx_map = initializer->get_tainting_map(); - if (not tx_map or py::len(basename_result) == 0) { + const auto tx_map = initializer->get_tainting_map(); + if (not tx_map or tx_map->empty() or py::len(basename_result) == 0) { return basename_result; } @@ -140,8 +140,8 @@ api_ospathdirname_aspect(const StrType& path) auto dirname = ospath.attr("dirname"); auto dirname_result = dirname(path); - auto tx_map = initializer->get_tainting_map(); - if (not tx_map or py::len(dirname_result) == 0) { + const auto tx_map = initializer->get_tainting_map(); + if (not tx_map or tx_map->empty() or py::len(dirname_result) == 0) { return dirname_result; } @@ -173,8 +173,8 @@ _forward_to_set_ranges_on_splitted(const char* function_name, const StrType& pat auto function = ospath.attr(function_name); auto function_result = function(path); - auto tx_map = initializer->get_tainting_map(); - if (not tx_map or py::len(function_result) == 0) { + const auto tx_map = initializer->get_tainting_map(); + if (not tx_map or tx_map->empty() or py::len(function_result) == 0) { return function_result; } @@ -225,8 +225,8 @@ api_ospathnormcase_aspect(const StrType& path) auto normcase = ospath.attr("normcase"); auto normcased = normcase(path); - auto tx_map = initializer->get_tainting_map(); - if (not tx_map) { + const auto tx_map = initializer->get_tainting_map(); + if (not tx_map or tx_map->empty()) { return normcased; } diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/Helpers.cpp b/ddtrace/appsec/_iast/_taint_tracking/Aspects/Helpers.cpp index b76a7d71e7e..b16f1d623bf 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/Helpers.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/Helpers.cpp @@ -10,9 +10,10 @@ namespace py = pybind11; /** * @brief This function is used to get the taint ranges for the given text object. * - * @tparam StrType - * @param text - * @return TaintRangeRefs + * @param string_method The string method to be used. + * @param candidate_text The text object for which the taint ranges are to be built. + * @param args The arguments to be passed to the string method. + * @param kwargs The keyword arguments to be passed to the string method. */ template StrType @@ -23,9 +24,9 @@ api_common_replace(const py::str& string_method, { bool ranges_error; TaintRangeRefs candidate_text_ranges; - TaintRangeMapType* tx_map = initializer->get_tainting_map(); StrType res = py::getattr(candidate_text, string_method)(*args, **kwargs); + const auto tx_map = initializer->get_tainting_map(); if (not tx_map or tx_map->empty()) { return res; } @@ -193,7 +194,7 @@ py::bytearray api_convert_escaped_text_to_taint_text_ba(const py::bytearray& taint_escaped_text, TaintRangeRefs ranges_orig) { - auto tx_map = initializer->get_tainting_map(); + const auto tx_map = initializer->get_tainting_map(); py::bytes bytes_text = py::bytes() + taint_escaped_text; @@ -207,7 +208,7 @@ template StrType api_convert_escaped_text_to_taint_text(const StrType& taint_escaped_text, TaintRangeRefs ranges_orig) { - auto tx_map = initializer->get_tainting_map(); + const auto tx_map = initializer->get_tainting_map(); std::tuple result = _convert_escaped_text_to_taint_text(taint_escaped_text, ranges_orig); StrType result_text = get<0>(result); @@ -343,13 +344,14 @@ bool set_ranges_on_splitted(const StrType& source_str, const TaintRangeRefs& source_ranges, const py::list& split_result, - TaintRangeMapType* tx_map, + const TaintRangeMapTypePtr& tx_map, bool include_separator) { bool some_set = false; // Some quick shortcuts - if (source_ranges.empty() or py::len(split_result) == 0 or py::len(source_str) == 0 or not tx_map) { + if (source_ranges.empty() or py::len(split_result) == 0 or py::len(source_str) == 0 or not tx_map or + tx_map->empty()) { return false; } @@ -401,7 +403,10 @@ api_set_ranges_on_splitted(const StrType& source_str, const py::list& split_result, bool include_separator) { - TaintRangeMapType* tx_map = initializer->get_tainting_map(); + const auto tx_map = initializer->get_tainting_map(); + if (not tx_map or tx_map->empty()) { + return false; + } return set_ranges_on_splitted(source_str, source_ranges, split_result, tx_map, include_separator); } diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/Helpers.h b/ddtrace/appsec/_iast/_taint_tracking/Aspects/Helpers.h index 3a8ddbf83ed..b4d4e603da7 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/Helpers.h +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/Helpers.h @@ -57,7 +57,7 @@ bool set_ranges_on_splitted(const StrType& source_str, const TaintRangeRefs& source_ranges, const py::list& split_result, - TaintRangeMapType* tx_map, + const TaintRangeMapTypePtr& tx_map, bool include_separator = false); template diff --git a/ddtrace/appsec/_iast/_taint_tracking/Initializer/Initializer.cpp b/ddtrace/appsec/_iast/_taint_tracking/Initializer/Initializer.cpp index c72d494741b..8236cbf5503 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Initializer/Initializer.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/Initializer/Initializer.cpp @@ -7,7 +7,7 @@ using namespace pybind11::literals; thread_local struct ThreadContextCache_ { - size_t tx_id = 0; + TaintRangeMapTypePtr tx_map = nullptr; } ThreadContextCache; Initializer::Initializer() @@ -23,23 +23,23 @@ Initializer::Initializer() } } -TaintRangeMapType* +TaintRangeMapTypePtr Initializer::create_tainting_map() { - auto map_ptr = new TaintRangeMapType(); - active_map_addreses.insert(map_ptr); + auto map_ptr = make_shared(); + active_map_addreses[map_ptr.get()] = map_ptr; return map_ptr; } void -Initializer::free_tainting_map(TaintRangeMapType* tx_map) +Initializer::clear_tainting_map(const TaintRangeMapTypePtr& tx_map) { if (not tx_map) return; - auto it = active_map_addreses.find(tx_map); + auto it = active_map_addreses.find(tx_map.get()); if (it == active_map_addreses.end()) { - // Map wasn't in the set, do nothing + // Map wasn't in the active addresses, do nothing return; } @@ -48,24 +48,22 @@ Initializer::free_tainting_map(TaintRangeMapType* tx_map) } tx_map->clear(); - delete tx_map; - active_map_addreses.erase(it); } // User must check for nullptr return -TaintRangeMapType* +TaintRangeMapTypePtr Initializer::get_tainting_map() { - return (TaintRangeMapType*)ThreadContextCache.tx_id; + return ThreadContextCache.tx_map; } void Initializer::clear_tainting_maps() { // Need to copy because free_tainting_map changes the set inside the iteration - auto map_addresses_copy = initializer->active_map_addreses; - for (auto map_ptr : map_addresses_copy) { - free_tainting_map((TaintRangeMapType*)map_ptr); + for (auto& [fst, snd] : initializer->active_map_addreses) { + clear_tainting_map(snd); + snd = nullptr; } active_map_addreses.clear(); } @@ -203,29 +201,21 @@ Initializer::release_taint_range(TaintRangePtr rangeptr) void Initializer::create_context() { - if (ThreadContextCache.tx_id != 0) { - // Destroy the current context - destroy_context(); + if (ThreadContextCache.tx_map != nullptr) { + // Reset the current context + reset_context(); } // Create a new taint_map auto map_ptr = create_tainting_map(); - ThreadContextCache.tx_id = (size_t)map_ptr; -} - -void -Initializer::destroy_context() -{ - free_tainting_map((TaintRangeMapType*)ThreadContextCache.tx_id); - ThreadContextCache.tx_id = 0; + ThreadContextCache.tx_map = map_ptr; } void Initializer::reset_context() { - // lock_guard lock(contexts_mutex); - ThreadContextCache.tx_id = 0; clear_tainting_maps(); + ThreadContextCache.tx_map = nullptr; } // Created in the PYBIND11_MODULE in _native.cpp @@ -244,5 +234,4 @@ pyexport_initializer(py::module& m) m.def( "create_context", []() { return initializer->create_context(); }, py::return_value_policy::reference); m.def("reset_context", [] { initializer->reset_context(); }); - m.def("destroy_context", [] { initializer->destroy_context(); }); } diff --git a/ddtrace/appsec/_iast/_taint_tracking/Initializer/Initializer.h b/ddtrace/appsec/_iast/_taint_tracking/Initializer/Initializer.h index 4dcb071243a..8146b7f47fd 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Initializer/Initializer.h +++ b/ddtrace/appsec/_iast/_taint_tracking/Initializer/Initializer.h @@ -7,7 +7,6 @@ #include #include -#include using namespace std; @@ -22,7 +21,9 @@ class Initializer static constexpr int TAINTEDOBJECTS_STACK_SIZE = 4096; stack available_taintedobjects_stack; stack available_ranges_stack; - unordered_set active_map_addreses; + // This is a map instead of a set so we can change the contents on iteration; otherwise + // keys and values are the same pointer. + unordered_map active_map_addreses; public: /** @@ -35,21 +36,21 @@ class Initializer * * @return A pointer to the created taint range map. */ - TaintRangeMapType* create_tainting_map(); + TaintRangeMapTypePtr create_tainting_map(); /** - * Frees a taint range map. + * Clears a taint range map. * * @param tx_map The taint range map to be freed. */ - void free_tainting_map(TaintRangeMapType* tx_map); + void clear_tainting_map(const TaintRangeMapTypePtr& tx_map); /** * Gets the current taint range map. * * @return A pointer to the current taint range map. */ - static TaintRangeMapType* get_tainting_map(); + static TaintRangeMapTypePtr get_tainting_map(); /** * Clears all active taint maps. @@ -84,11 +85,6 @@ class Initializer */ void create_context(); - /** - * Destroys the current taint tracking context. - */ - void destroy_context(); - /** * Resets the current taint tracking context. */ diff --git a/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/TaintRange.cpp b/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/TaintRange.cpp index 4883a84213a..006a71ddfb6 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/TaintRange.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/TaintRange.cpp @@ -70,7 +70,7 @@ api_shift_taint_ranges(const TaintRangeRefs& source_taint_ranges, RANGE_START of py::object api_set_ranges(py::object& str, const TaintRangeRefs& ranges) { - auto tx_map = initializer->get_tainting_map(); + const auto tx_map = initializer->get_tainting_map(); if (not tx_map) { throw py::value_error(MSG_ERROR_TAINT_MAP); @@ -109,7 +109,7 @@ api_set_ranges_from_values(PyObject* self, PyObject* const* args, Py_ssize_t nar if (nargs == 5) { PyObject* tainted_object = args[0]; - TaintRangeMapType* tx_map = initializer->get_tainting_map(); + const auto tx_map = initializer->get_tainting_map(); if (not tx_map) { py::set_error(PyExc_ValueError, MSG_ERROR_TAINT_MAP); return nullptr; @@ -147,7 +147,7 @@ api_set_ranges_from_values(PyObject* self, PyObject* const* args, Py_ssize_t nar } std::pair -get_ranges(PyObject* string_input, TaintRangeMapType* tx_map) +get_ranges(PyObject* string_input, const TaintRangeMapTypePtr& tx_map) { TaintRangeRefs result; if (not is_text(string_input)) @@ -170,7 +170,7 @@ get_ranges(PyObject* string_input, TaintRangeMapType* tx_map) } bool -set_ranges(PyObject* str, const TaintRangeRefs& ranges, TaintRangeMapType* tx_map) +set_ranges(PyObject* str, const TaintRangeRefs& ranges, const TaintRangeMapTypePtr& tx_map) { if (ranges.empty()) { return false; @@ -202,7 +202,7 @@ are_all_text_all_ranges(PyObject* candidate_text, const py::tuple& parameter_lis bool ranges_error; TaintRangeRefs candidate_text_ranges, all_ranges; - TaintRangeMapType* tx_map = initializer->get_tainting_map(); + const auto tx_map = initializer->get_tainting_map(); if (not tx_map or tx_map->empty()) { return { {}, {} }; } @@ -247,7 +247,7 @@ api_get_ranges(const py::object& string_input) { bool ranges_error; TaintRangeRefs ranges; - TaintRangeMapType* tx_map = initializer->get_tainting_map(); + const auto tx_map = initializer->get_tainting_map(); if (not tx_map) { throw py::value_error(MSG_ERROR_TAINT_MAP); @@ -266,7 +266,7 @@ api_copy_ranges_from_strings(py::object& str_1, py::object& str_2) bool ranges_error, result; TaintRangeRefs ranges; - TaintRangeMapType* tx_map = initializer->get_tainting_map(); + const auto tx_map = initializer->get_tainting_map(); if (not tx_map) { py::set_error(PyExc_ValueError, MSG_ERROR_TAINT_MAP); @@ -289,7 +289,7 @@ api_copy_and_shift_ranges_from_strings(py::object& str_1, py::object& str_2, int { bool ranges_error, result; TaintRangeRefs ranges; - TaintRangeMapType* tx_map = initializer->get_tainting_map(); + const auto tx_map = initializer->get_tainting_map(); if (not tx_map) { py::set_error(PyExc_ValueError, MSG_ERROR_TAINT_MAP); return; @@ -306,7 +306,7 @@ api_copy_and_shift_ranges_from_strings(py::object& str_1, py::object& str_2, int } TaintedObjectPtr -get_tainted_object(PyObject* str, TaintRangeMapType* tx_map) +get_tainted_object(PyObject* str, const TaintRangeMapTypePtr& tx_map) { if (not str) return nullptr; @@ -347,7 +347,7 @@ get_internal_hash(PyObject* obj) } void -set_tainted_object(PyObject* str, TaintedObjectPtr tainted_object, TaintRangeMapType* tx_taint_map) +set_tainted_object(PyObject* str, TaintedObjectPtr tainted_object, const TaintRangeMapTypePtr& tx_taint_map) { if (not str or not is_text(str)) { return; diff --git a/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/TaintRange.h b/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/TaintRange.h index d076dc59846..34186ca53c8 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/TaintRange.h +++ b/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/TaintRange.h @@ -32,6 +32,9 @@ using TaintRangeMapType = std::map; +// using TaintRangeMapTypePtr = TaintRangeMapType*; + struct TaintRange { RANGE_START start = 0; @@ -88,10 +91,10 @@ TaintRangeRefs api_shift_taint_ranges(const TaintRangeRefs&, RANGE_START offset, RANGE_LENGTH new_length); std::pair -get_ranges(PyObject* string_input, TaintRangeMapType* tx_map); +get_ranges(PyObject* string_input, const TaintRangeMapTypePtr& tx_map); bool -set_ranges(PyObject* str, const TaintRangeRefs& ranges, TaintRangeMapType* tx_map); +set_ranges(PyObject* str, const TaintRangeRefs& ranges, const TaintRangeMapTypePtr& tx_map); py::object api_set_ranges(py::object& str, const TaintRangeRefs& ranges); @@ -133,7 +136,7 @@ api_is_unicode_and_not_fast_tainted(const py::object str) } TaintedObject* -get_tainted_object(PyObject* str, TaintRangeMapType* tx_taint_map); +get_tainted_object(PyObject* str, const TaintRangeMapTypePtr& tx_taint_map); Py_hash_t bytearray_hash(PyObject* bytearray); @@ -142,7 +145,7 @@ Py_hash_t get_internal_hash(PyObject* obj); void -set_tainted_object(PyObject* str, TaintedObjectPtr tainted_object, TaintRangeMapType* tx_taint_map); +set_tainted_object(PyObject* str, TaintedObjectPtr tainted_object, const TaintRangeMapTypePtr& tx_taint_map); void pyexport_taintrange(py::module& m); diff --git a/ddtrace/appsec/_iast/_taint_tracking/TaintedOps/TaintedOps.cpp b/ddtrace/appsec/_iast/_taint_tracking/TaintedOps/TaintedOps.cpp index a3214059c0d..220303fb39c 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/TaintedOps/TaintedOps.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/TaintedOps/TaintedOps.cpp @@ -11,7 +11,7 @@ api_new_pyobject_id(PyObject* self, PyObject* const* args, Py_ssize_t nargs) } bool -is_tainted(PyObject* tainted_object, TaintRangeMapType* tx_taint_map) +is_tainted(PyObject* tainted_object, const TaintRangeMapTypePtr& tx_taint_map) { const auto& to_initial = get_tainted_object(tainted_object, tx_taint_map); if (to_initial and !to_initial->get_ranges().empty()) { @@ -24,7 +24,7 @@ bool api_is_tainted(py::object tainted_object) { if (tainted_object) { - TaintRangeMapType* tx_map = initializer->get_tainting_map(); + const auto tx_map = initializer->get_tainting_map(); if (not tx_map or tx_map->empty()) { return false; } diff --git a/ddtrace/appsec/_iast/_taint_tracking/TaintedOps/TaintedOps.h b/ddtrace/appsec/_iast/_taint_tracking/TaintedOps/TaintedOps.h index 8ce24180617..5b3c88506ef 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/TaintedOps/TaintedOps.h +++ b/ddtrace/appsec/_iast/_taint_tracking/TaintedOps/TaintedOps.h @@ -14,7 +14,7 @@ PyObject* api_new_pyobject_id(PyObject* self, PyObject* const* args, Py_ssize_t nargs); bool -is_tainted(PyObject* tainted_object, TaintRangeMapType* tx_taint_map); +is_tainted(PyObject* tainted_object, const TaintRangeMapTypePtr& tx_taint_map); bool api_is_tainted(py::object tainted_object); diff --git a/ddtrace/appsec/_iast/_taint_tracking/__init__.py b/ddtrace/appsec/_iast/_taint_tracking/__init__.py index ad31573add6..e2e24c7aadd 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/__init__.py +++ b/ddtrace/appsec/_iast/_taint_tracking/__init__.py @@ -38,7 +38,6 @@ from ._native.initializer import active_map_addreses_size from ._native.initializer import create_context from ._native.initializer import debug_taint_map - from ._native.initializer import destroy_context from ._native.initializer import initializer_size from ._native.initializer import num_objects_tainted from ._native.initializer import reset_context @@ -84,7 +83,6 @@ "set_fast_tainted_if_notinterned_unicode", "aspect_helpers", "reset_context", - "destroy_context", "initializer_size", "active_map_addreses_size", "create_context", diff --git a/releasenotes/notes/iast-segfaults-fix-d25c5c132dd8482e.yaml b/releasenotes/notes/iast-segfaults-fix-d25c5c132dd8482e.yaml new file mode 100644 index 00000000000..a52989508e3 --- /dev/null +++ b/releasenotes/notes/iast-segfaults-fix-d25c5c132dd8482e.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + Code Security: fix a potential memory corruption when the context was reset. diff --git a/tests/appsec/iast/aspects/test_add_aspect.py b/tests/appsec/iast/aspects/test_add_aspect.py index eb27ced721f..71ca61fcf2f 100644 --- a/tests/appsec/iast/aspects/test_add_aspect.py +++ b/tests/appsec/iast/aspects/test_add_aspect.py @@ -8,9 +8,9 @@ from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import Source from ddtrace.appsec._iast._taint_tracking import create_context -from ddtrace.appsec._iast._taint_tracking import destroy_context from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted +from ddtrace.appsec._iast._taint_tracking import reset_context from ddtrace.appsec._iast._taint_tracking import taint_pyobject from ddtrace.appsec._iast._taint_tracking import taint_ranges_as_evidence_info from ddtrace.appsec._iast._taint_tracking._native.taint_tracking import TaintRange_ @@ -335,7 +335,7 @@ def test_taint_object_error_with_no_context(log_level, iast_debug, expected_log_ ranges_result = get_tainted_ranges(result) assert len(ranges_result) == 1 - destroy_context() + reset_context() with override_env({IAST.ENV_DEBUG: iast_debug}), caplog.at_level(log_level): result = taint_pyobject( pyobject=string_to_taint, @@ -378,7 +378,7 @@ def test_get_ranges_from_object_with_no_context(): source_origin=OriginType.PARAMETER, ) - destroy_context() + reset_context() ranges_result = get_tainted_ranges(result) assert len(ranges_result) == 0 @@ -395,7 +395,7 @@ def test_propagate_ranges_with_no_context(caplog): source_origin=OriginType.PARAMETER, ) - destroy_context() + reset_context() with override_env({IAST.ENV_DEBUG: "true"}), caplog.at_level(logging.DEBUG): result_2 = add_aspect(result, "another_string") diff --git a/tests/appsec/iast/aspects/test_bytearray_extend_aspect.py b/tests/appsec/iast/aspects/test_bytearray_extend_aspect.py index c79ec2eb140..7c4fc89ec9a 100644 --- a/tests/appsec/iast/aspects/test_bytearray_extend_aspect.py +++ b/tests/appsec/iast/aspects/test_bytearray_extend_aspect.py @@ -8,8 +8,8 @@ from ddtrace.appsec._iast._taint_tracking import Source from ddtrace.appsec._iast._taint_tracking import TaintRange from ddtrace.appsec._iast._taint_tracking import create_context -from ddtrace.appsec._iast._taint_tracking import destroy_context from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking import reset_context from ddtrace.appsec._iast._taint_tracking import taint_pyobject from tests.appsec.iast.aspects.conftest import _iast_patched_module from tests.utils import override_env @@ -110,7 +110,7 @@ def test_propagate_ranges_with_no_context(caplog): ba2 = taint_pyobject( pyobject=bytearray(b"456"), source_name="test", source_value="foo", source_origin=OriginType.PARAMETER ) - destroy_context() + reset_context() with override_env({IAST.ENV_DEBUG: "true"}), caplog.at_level(logging.DEBUG): result = mod.do_bytearray_extend(ba1, ba2) assert result == bytearray(b"123456") diff --git a/tests/appsec/iast/aspects/test_format_aspect_fixtures.py b/tests/appsec/iast/aspects/test_format_aspect_fixtures.py index e280a872754..71f63cc1c16 100644 --- a/tests/appsec/iast/aspects/test_format_aspect_fixtures.py +++ b/tests/appsec/iast/aspects/test_format_aspect_fixtures.py @@ -11,8 +11,8 @@ from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import as_formatted_evidence from ddtrace.appsec._iast._taint_tracking import create_context -from ddtrace.appsec._iast._taint_tracking import destroy_context from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking import reset_context from ddtrace.appsec._iast._taint_tracking import taint_pyobject from tests.appsec.iast.aspects.aspect_utils import BaseReplacement from tests.appsec.iast.aspects.aspect_utils import create_taint_range_with_format @@ -245,7 +245,7 @@ def test_propagate_ranges_with_no_context(caplog): source_value=string_to_taint, source_origin=OriginType.PARAMETER, ) - destroy_context() + reset_context() with override_env({IAST.ENV_DEBUG: "true"}), caplog.at_level(logging.DEBUG): result_2 = mod.do_args_kwargs_4(string_input, 6, test_var=1) diff --git a/tests/appsec/iast/aspects/test_index_aspect_fixtures.py b/tests/appsec/iast/aspects/test_index_aspect_fixtures.py index 0d19b82a709..5b6671958b9 100644 --- a/tests/appsec/iast/aspects/test_index_aspect_fixtures.py +++ b/tests/appsec/iast/aspects/test_index_aspect_fixtures.py @@ -6,8 +6,8 @@ from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import create_context -from ddtrace.appsec._iast._taint_tracking import destroy_context from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking import reset_context from ddtrace.appsec._iast._taint_tracking import taint_pyobject from tests.appsec.iast.aspects.conftest import _iast_patched_module from tests.utils import override_env @@ -92,7 +92,7 @@ def test_propagate_ranges_with_no_context(caplog): ) assert get_tainted_ranges(string_input) - destroy_context() + reset_context() with override_env({IAST.ENV_DEBUG: "true"}), caplog.at_level(logging.DEBUG): result = mod.do_index(string_input, 3) assert result == "d" diff --git a/tests/appsec/iast/aspects/test_join_aspect_fixtures.py b/tests/appsec/iast/aspects/test_join_aspect_fixtures.py index 626673e9bdc..66c285b3183 100644 --- a/tests/appsec/iast/aspects/test_join_aspect_fixtures.py +++ b/tests/appsec/iast/aspects/test_join_aspect_fixtures.py @@ -7,8 +7,8 @@ from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import create_context -from ddtrace.appsec._iast._taint_tracking import destroy_context from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking import reset_context from ddtrace.appsec._iast._taint_tracking import taint_pyobject from tests.appsec.iast.aspects.conftest import _iast_patched_module from tests.utils import override_env @@ -445,7 +445,7 @@ def test_propagate_ranges_with_no_context(caplog): pyobject="-joiner-", source_name="joiner", source_value="foo", source_origin=OriginType.PARAMETER ) it = ["a", "b", "c"] - destroy_context() + reset_context() with override_env({IAST.ENV_DEBUG: "true"}), caplog.at_level(logging.DEBUG): result = mod.do_join(string_input, it) assert result == "a-joiner-b-joiner-c" @@ -464,7 +464,7 @@ def test_propagate_ranges_with_no_context_with_var(caplog): "b", "c", ] - destroy_context() + reset_context() with override_env({IAST.ENV_DEBUG: "true"}), caplog.at_level(logging.DEBUG): result = mod.do_join(string_input, it) assert result == "a-joiner-b-joiner-c" @@ -482,7 +482,7 @@ def test_propagate_ranges_with_no_context_with_equal_var(caplog): pyobject="abcdef", source_name="joined", source_value="abcdef", source_origin=OriginType.PARAMETER ) - destroy_context() + reset_context() with override_env({IAST.ENV_DEBUG: "true"}), caplog.at_level(logging.DEBUG): result = mod.do_join(string_input, [a_tainted, a_tainted, a_tainted]) assert result == "abcdef-joiner-abcdef-joiner-abcdef" diff --git a/tests/appsec/iast/aspects/test_ospath_aspects_fixtures.py b/tests/appsec/iast/aspects/test_ospath_aspects_fixtures.py index f36bf6243d6..e31f4d7447a 100644 --- a/tests/appsec/iast/aspects/test_ospath_aspects_fixtures.py +++ b/tests/appsec/iast/aspects/test_ospath_aspects_fixtures.py @@ -10,8 +10,8 @@ from ddtrace.appsec._iast._taint_tracking import Source from ddtrace.appsec._iast._taint_tracking import TaintRange from ddtrace.appsec._iast._taint_tracking import create_context -from ddtrace.appsec._iast._taint_tracking import destroy_context from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking import reset_context from ddtrace.appsec._iast._taint_tracking import taint_pyobject from tests.appsec.iast.aspects.conftest import _iast_patched_module from tests.utils import override_env @@ -161,7 +161,7 @@ def test_propagate_ranges_with_no_context(caplog): ) assert get_tainted_ranges(string_input) - destroy_context() + reset_context() with override_env({IAST.ENV_DEBUG: "true"}), caplog.at_level(logging.DEBUG): result = mod.do_os_path_join(string_input, "bar") assert result == "abcde/bar" diff --git a/tests/appsec/iast/aspects/test_slice_aspect_fixtures.py b/tests/appsec/iast/aspects/test_slice_aspect_fixtures.py index 146feb40330..6d011769c89 100644 --- a/tests/appsec/iast/aspects/test_slice_aspect_fixtures.py +++ b/tests/appsec/iast/aspects/test_slice_aspect_fixtures.py @@ -7,8 +7,8 @@ from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import create_context -from ddtrace.appsec._iast._taint_tracking import destroy_context from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges +from ddtrace.appsec._iast._taint_tracking import reset_context from ddtrace.appsec._iast._taint_tracking import taint_pyobject from tests.appsec.iast.aspects.conftest import _iast_patched_module from tests.utils import override_env @@ -318,7 +318,7 @@ def test_propagate_ranges_with_no_context(caplog): source_origin=OriginType.PARAMETER, ) - destroy_context() + reset_context() with override_env({IAST.ENV_DEBUG: "true"}), caplog.at_level(logging.DEBUG): result = mod.do_slice(tainted_input, 0, 3, None) assert result == "abc" diff --git a/tests/appsec/iast/aspects/test_split_aspect.py b/tests/appsec/iast/aspects/test_split_aspect.py index 6ec65b90be5..923df06eed4 100644 --- a/tests/appsec/iast/aspects/test_split_aspect.py +++ b/tests/appsec/iast/aspects/test_split_aspect.py @@ -9,7 +9,7 @@ from ddtrace.appsec._iast._taint_tracking import _aspect_split from ddtrace.appsec._iast._taint_tracking import _aspect_splitlines from ddtrace.appsec._iast._taint_tracking import create_context -from ddtrace.appsec._iast._taint_tracking import destroy_context +from ddtrace.appsec._iast._taint_tracking import reset_context from ddtrace.appsec._iast._taint_tracking import taint_pyobject from ddtrace.appsec._iast._taint_tracking._native.taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking._native.taint_tracking import Source @@ -169,7 +169,7 @@ def test_propagate_ranges_with_no_context(caplog): ) assert get_ranges(string_input) - destroy_context() + reset_context() with override_env({IAST.ENV_DEBUG: "true"}), caplog.at_level(logging.DEBUG): result = _aspect_split(string_input, "|") assert result == ["abc", "def"] diff --git a/tests/appsec/iast/taint_tracking/test_taint_tracking.py b/tests/appsec/iast/taint_tracking/test_taint_tracking.py index acbf8089d78..4d5b9653675 100644 --- a/tests/appsec/iast/taint_tracking/test_taint_tracking.py +++ b/tests/appsec/iast/taint_tracking/test_taint_tracking.py @@ -12,8 +12,8 @@ from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import Source from ddtrace.appsec._iast._taint_tracking import TaintRange - from ddtrace.appsec._iast._taint_tracking import destroy_context from ddtrace.appsec._iast._taint_tracking import num_objects_tainted + from ddtrace.appsec._iast._taint_tracking import reset_context from ddtrace.appsec._iast._taint_tracking import set_ranges from ddtrace.appsec._iast._taint_tracking import taint_pyobject from ddtrace.appsec._iast._taint_tracking import taint_ranges_as_evidence_info @@ -42,7 +42,7 @@ def test_taint_ranges_as_evidence_info_all_tainted(): @pytest.mark.skip_iast_check_logs def test_taint_object_with_no_context_should_be_noop(): - destroy_context() + reset_context() arg = "all tainted" tainted_text = taint_pyobject(arg, source_name="request_body", source_value=arg, source_origin=OriginType.PARAMETER) assert tainted_text == arg @@ -51,7 +51,7 @@ def test_taint_object_with_no_context_should_be_noop(): @pytest.mark.skip_iast_check_logs def test_propagate_ranges_with_no_context(caplog): - destroy_context() + reset_context() with override_env({IAST.ENV_DEBUG: "true"}), caplog.at_level(logging.DEBUG): string_input = taint_pyobject( pyobject="abcde", source_name="abcde", source_value="abcde", source_origin=OriginType.PARAMETER @@ -63,7 +63,7 @@ def test_propagate_ranges_with_no_context(caplog): @pytest.mark.skip_iast_check_logs def test_call_to_set_ranges_directly_raises_a_exception(caplog): - destroy_context() + reset_context() input_str = "abcde" with pytest.raises(ValueError) as excinfo: set_ranges( From fa50018333f317ffac9b1c75464080a3cf5361f7 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Thu, 16 May 2024 15:19:12 +0200 Subject: [PATCH 057/104] feat(iast): remove deprecated functions (#9281) Remove deprecated functions ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. - [x] If change touches code that signs or publishes builds or packages, or handles credentials of any kind, I've requested a review from `@DataDog/security-design-and-guidance`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../appsec/_iast/_taint_tracking/__init__.py | 35 -------- ddtrace/appsec/_iast/reporter.py | 3 +- tests/appsec/iast/aspects/test_add_aspect.py | 79 +------------------ .../taint_tracking/test_taint_tracking.py | 58 ++++++++------ .../contrib/django/django_app/appsec_urls.py | 6 +- 5 files changed, 41 insertions(+), 140 deletions(-) diff --git a/ddtrace/appsec/_iast/_taint_tracking/__init__.py b/ddtrace/appsec/_iast/_taint_tracking/__init__.py index e2e24c7aadd..419c741d4ae 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/__init__.py +++ b/ddtrace/appsec/_iast/_taint_tracking/__init__.py @@ -1,9 +1,6 @@ import os from typing import Any -from typing import Dict -from typing import List from typing import Tuple -from typing import Union from ddtrace.internal.logger import get_logger from ddtrace.internal.utils.formats import asbool @@ -178,35 +175,3 @@ def get_tainted_ranges(pyobject: Any) -> Tuple: except ValueError as e: iast_taint_log_error("Get ranges error (pyobject type %s): %s" % (type(pyobject), e)) return tuple() - - -def taint_ranges_as_evidence_info(pyobject: Any) -> Tuple[List[Dict[str, Union[Any, int]]], List[Source]]: - # TODO: This function is deprecated. - # Redaction migrated to `ddtrace.appsec._iast._evidence_redaction._sensitive_handler` but we need to migrate - # all vulnerabilities to use it first. - value_parts = [] - sources = list() - current_pos = 0 - tainted_ranges = get_tainted_ranges(pyobject) - if not len(tainted_ranges): - return ([{"value": pyobject}], list()) - - for _range in tainted_ranges: - if _range.start > current_pos: - value_parts.append({"value": pyobject[current_pos : _range.start]}) - - if _range.source not in sources: - sources.append(_range.source) - - value_parts.append( - { - "value": pyobject[_range.start : _range.start + _range.length], - "source": sources.index(_range.source), - } - ) - current_pos = _range.start + _range.length - - if current_pos < len(pyobject): - value_parts.append({"value": pyobject[current_pos:]}) - - return value_parts, sources diff --git a/ddtrace/appsec/_iast/reporter.py b/ddtrace/appsec/_iast/reporter.py index 90d334277bd..9c8bd28bc51 100644 --- a/ddtrace/appsec/_iast/reporter.py +++ b/ddtrace/appsec/_iast/reporter.py @@ -113,7 +113,8 @@ def __hash__(self) -> int: """ return reduce(operator.xor, (hash(obj) for obj in set(self.sources) | self.vulnerabilities)) - def taint_ranges_as_evidence_info(self, pyobject: Any) -> Tuple[List[Source], List[Dict]]: + @staticmethod + def taint_ranges_as_evidence_info(pyobject: Any) -> Tuple[List[Source], List[Dict]]: """ Extracts tainted ranges as evidence information. diff --git a/tests/appsec/iast/aspects/test_add_aspect.py b/tests/appsec/iast/aspects/test_add_aspect.py index 71ca61fcf2f..61da91b1479 100644 --- a/tests/appsec/iast/aspects/test_add_aspect.py +++ b/tests/appsec/iast/aspects/test_add_aspect.py @@ -6,13 +6,11 @@ from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast._taint_tracking import OriginType -from ddtrace.appsec._iast._taint_tracking import Source from ddtrace.appsec._iast._taint_tracking import create_context from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted from ddtrace.appsec._iast._taint_tracking import reset_context from ddtrace.appsec._iast._taint_tracking import taint_pyobject -from ddtrace.appsec._iast._taint_tracking import taint_ranges_as_evidence_info from ddtrace.appsec._iast._taint_tracking._native.taint_tracking import TaintRange_ import ddtrace.appsec._iast._taint_tracking.aspects as ddtrace_aspects from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect @@ -23,7 +21,7 @@ "obj1, obj2", [ (3.5, 3.3), - # (complex(2, 1), complex(3, 4)), + (complex(2, 1), complex(3, 4)), ("Hello ", "world"), ("🙀", "🌝"), (b"Hi", b""), @@ -234,81 +232,6 @@ def test_add_aspect_tainting_add_left_twice(obj1, obj2): assert ranges_result[0].length == 3 -def test_taint_ranges_as_evidence_info_nothing_tainted(): - text = "nothing tainted" - value_parts, sources = taint_ranges_as_evidence_info(text) - assert value_parts == [{"value": text}] - assert sources == [] - - -def test_taint_ranges_as_evidence_info_all_tainted(): - arg = "all tainted" - input_info = Source("request_body", arg, OriginType.PARAMETER) - tainted_text = taint_pyobject(arg, source_name="request_body", source_value=arg, source_origin=OriginType.PARAMETER) - value_parts, sources = taint_ranges_as_evidence_info(tainted_text) - assert value_parts == [{"value": tainted_text, "source": 0}] - assert sources == [input_info] - - -def test_taint_ranges_as_evidence_info_tainted_op1_add(): - arg = "tainted part" - input_info = Source("request_body", arg, OriginType.PARAMETER) - text = "|not tainted part|" - tainted_text = taint_pyobject(arg, source_name="request_body", source_value=arg, source_origin=OriginType.PARAMETER) - tainted_add_result = add_aspect(tainted_text, text) - - value_parts, sources = taint_ranges_as_evidence_info(tainted_add_result) - assert value_parts == [{"value": tainted_text, "source": 0}, {"value": text}] - assert sources == [input_info] - - -def test_taint_ranges_as_evidence_info_tainted_op2_add(): - arg = "tainted part" - input_info = Source("request_body", arg, OriginType.PARAMETER) - text = "|not tainted part|" - tainted_text = taint_pyobject(arg, source_name="request_body", source_value=arg, source_origin=OriginType.PARAMETER) - tainted_add_result = add_aspect(text, tainted_text) - - value_parts, sources = taint_ranges_as_evidence_info(tainted_add_result) - assert value_parts == [{"value": text}, {"value": tainted_text, "source": 0}] - assert sources == [input_info] - - -def test_taint_ranges_as_evidence_info_same_tainted_op1_and_op3_add(): - arg = "tainted part" - input_info = Source("request_body", arg, OriginType.PARAMETER) - text = "|not tainted part|" - tainted_text = taint_pyobject(arg, source_name="request_body", source_value=arg, source_origin=OriginType.PARAMETER) - tainted_add_result = add_aspect(tainted_text, add_aspect(text, tainted_text)) - - value_parts, sources = taint_ranges_as_evidence_info(tainted_add_result) - assert value_parts == [{"value": tainted_text, "source": 0}, {"value": text}, {"value": tainted_text, "source": 0}] - assert sources == [input_info] - - -def test_taint_ranges_as_evidence_info_different_tainted_op1_and_op3_add(): - arg1 = "tainted body" - arg2 = "tainted header" - input_info1 = Source("request_body", arg1, OriginType.PARAMETER) - input_info2 = Source("request_body", arg2, OriginType.PARAMETER) - text = "|not tainted part|" - tainted_text1 = taint_pyobject( - arg1, source_name="request_body", source_value=arg1, source_origin=OriginType.PARAMETER - ) - tainted_text2 = taint_pyobject( - arg2, source_name="request_body", source_value=arg2, source_origin=OriginType.PARAMETER - ) - tainted_add_result = add_aspect(tainted_text1, add_aspect(text, tainted_text2)) - - value_parts, sources = taint_ranges_as_evidence_info(tainted_add_result) - assert value_parts == [ - {"value": tainted_text1, "source": 0}, - {"value": text}, - {"value": tainted_text2, "source": 1}, - ] - assert sources == [input_info1, input_info2] - - @pytest.mark.skip_iast_check_logs @pytest.mark.parametrize( "log_level, iast_debug, expected_log_msg", diff --git a/tests/appsec/iast/taint_tracking/test_taint_tracking.py b/tests/appsec/iast/taint_tracking/test_taint_tracking.py index 4d5b9653675..731b40b9f28 100644 --- a/tests/appsec/iast/taint_tracking/test_taint_tracking.py +++ b/tests/appsec/iast/taint_tracking/test_taint_tracking.py @@ -5,18 +5,18 @@ from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast import oce +from ddtrace.appsec._iast.reporter import IastSpanReporter +from ddtrace.appsec._iast.reporter import Source from tests.utils import override_env with override_env({"DD_IAST_ENABLED": "True"}): from ddtrace.appsec._iast._taint_tracking import OriginType - from ddtrace.appsec._iast._taint_tracking import Source from ddtrace.appsec._iast._taint_tracking import TaintRange from ddtrace.appsec._iast._taint_tracking import num_objects_tainted from ddtrace.appsec._iast._taint_tracking import reset_context from ddtrace.appsec._iast._taint_tracking import set_ranges from ddtrace.appsec._iast._taint_tracking import taint_pyobject - from ddtrace.appsec._iast._taint_tracking import taint_ranges_as_evidence_info from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect @@ -26,17 +26,17 @@ def setup(): def test_taint_ranges_as_evidence_info_nothing_tainted(): text = "nothing tainted" - value_parts, sources = taint_ranges_as_evidence_info(text) - assert value_parts == [{"value": text}] + sources, value_parts = IastSpanReporter.taint_ranges_as_evidence_info(text) + assert value_parts == [] assert sources == [] def test_taint_ranges_as_evidence_info_all_tainted(): arg = "all tainted" - input_info = Source("request_body", arg, OriginType.PARAMETER) + input_info = Source(origin=OriginType.PARAMETER, name="request_body", value=arg) tainted_text = taint_pyobject(arg, source_name="request_body", source_value=arg, source_origin=OriginType.PARAMETER) - value_parts, sources = taint_ranges_as_evidence_info(tainted_text) - assert value_parts == [{"value": tainted_text, "source": 0}] + sources, value_parts = IastSpanReporter.taint_ranges_as_evidence_info(tainted_text) + assert value_parts == [{"start": 0, "end": 11, "length": 11, "source": input_info}] assert sources == [input_info] @@ -63,57 +63,70 @@ def test_propagate_ranges_with_no_context(caplog): @pytest.mark.skip_iast_check_logs def test_call_to_set_ranges_directly_raises_a_exception(caplog): + from ddtrace.appsec._iast._taint_tracking import Source as TaintRangeSource + reset_context() input_str = "abcde" with pytest.raises(ValueError) as excinfo: set_ranges( input_str, - [TaintRange(0, len(input_str), Source(input_str, "sample_value", OriginType.PARAMETER))], + [TaintRange(0, len(input_str), TaintRangeSource(input_str, "sample_value", OriginType.PARAMETER))], ) assert str(excinfo.value).startswith("[IAST] Tainted Map isn't initialized") def test_taint_ranges_as_evidence_info_tainted_op1_add(): arg = "tainted part" - input_info = Source("request_body", arg, OriginType.PARAMETER) + input_info = Source(origin=OriginType.PARAMETER, name="request_body", value=arg) text = "|not tainted part|" tainted_text = taint_pyobject(arg, source_name="request_body", source_value=arg, source_origin=OriginType.PARAMETER) tainted_add_result = add_aspect(tainted_text, text) - value_parts, sources = taint_ranges_as_evidence_info(tainted_add_result) - assert value_parts == [{"value": tainted_text, "source": 0}, {"value": text}] + sources, value_parts = IastSpanReporter.taint_ranges_as_evidence_info(tainted_add_result) + assert value_parts == [{"start": 0, "end": 12, "length": 12, "source": input_info}] assert sources == [input_info] def test_taint_ranges_as_evidence_info_tainted_op2_add(): arg = "tainted part" - input_info = Source("request_body", arg, OriginType.PARAMETER) + input_info = Source(origin=OriginType.PARAMETER, name="request_body", value=arg) text = "|not tainted part|" tainted_text = taint_pyobject(arg, source_name="request_body", source_value=arg, source_origin=OriginType.PARAMETER) tainted_add_result = add_aspect(text, tainted_text) - value_parts, sources = taint_ranges_as_evidence_info(tainted_add_result) - assert value_parts == [{"value": text}, {"value": tainted_text, "source": 0}] + sources, value_parts = IastSpanReporter.taint_ranges_as_evidence_info(tainted_add_result) + assert ( + value_parts + == [{"end": 30, "length": 12, "source": input_info, "start": 18}] + != [{"end": 12, "length": 12, "source": input_info, "start": 0}] + ) assert sources == [input_info] def test_taint_ranges_as_evidence_info_same_tainted_op1_and_op3_add(): arg = "tainted part" - input_info = Source("request_body", arg, OriginType.PARAMETER) + input_info = Source(origin=OriginType.PARAMETER, name="request_body", value=arg) text = "|not tainted part|" tainted_text = taint_pyobject(arg, source_name="request_body", source_value=arg, source_origin=OriginType.PARAMETER) tainted_add_result = add_aspect(tainted_text, add_aspect(text, tainted_text)) - value_parts, sources = taint_ranges_as_evidence_info(tainted_add_result) - assert value_parts == [{"value": tainted_text, "source": 0}, {"value": text}, {"value": tainted_text, "source": 0}] + ( + sources, + value_parts, + ) = IastSpanReporter.taint_ranges_as_evidence_info(tainted_add_result) + assert value_parts == [ + {"end": 12, "length": 12, "source": input_info, "start": 0}, + {"end": 42, "length": 12, "source": input_info, "start": 30}, + ] + assert sources == [input_info] def test_taint_ranges_as_evidence_info_different_tainted_op1_and_op3_add(): arg1 = "tainted body" arg2 = "tainted header" - input_info1 = Source("request_body", arg1, OriginType.PARAMETER) - input_info2 = Source("request_body", arg2, OriginType.PARAMETER) + input_info1 = Source(origin=OriginType.PARAMETER, name="request_body", value=arg1) + input_info2 = Source(origin=OriginType.PARAMETER, name="request_body", value=arg2) text = "|not tainted part|" tainted_text1 = taint_pyobject( arg1, source_name="request_body", source_value=arg1, source_origin=OriginType.PARAMETER @@ -123,10 +136,9 @@ def test_taint_ranges_as_evidence_info_different_tainted_op1_and_op3_add(): ) tainted_add_result = add_aspect(tainted_text1, add_aspect(text, tainted_text2)) - value_parts, sources = taint_ranges_as_evidence_info(tainted_add_result) + sources, value_parts = IastSpanReporter.taint_ranges_as_evidence_info(tainted_add_result) assert value_parts == [ - {"value": tainted_text1, "source": 0}, - {"value": text}, - {"value": tainted_text2, "source": 1}, + {"end": 12, "length": 12, "source": input_info1, "start": 0}, + {"end": 44, "length": 14, "source": input_info2, "start": 30}, ] assert sources == [input_info1, input_info2] diff --git a/tests/contrib/django/django_app/appsec_urls.py b/tests/contrib/django/django_app/appsec_urls.py index ffdbd413936..2d369b1b948 100644 --- a/tests/contrib/django/django_app/appsec_urls.py +++ b/tests/contrib/django/django_app/appsec_urls.py @@ -124,12 +124,12 @@ def taint_checking_enabled_view(request): with override_env({"DD_IAST_ENABLED": "True"}): from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted - from ddtrace.appsec._iast._taint_tracking import taint_ranges_as_evidence_info + from ddtrace.appsec._iast.reporter import IastSpanReporter def assert_origin_path(path): # type: (Any) -> None assert is_pyobject_tainted(path) - result = taint_ranges_as_evidence_info(path) - assert result[1][0].origin == OriginType.PATH + sources, tainted_ranges_to_dict = IastSpanReporter.taint_ranges_as_evidence_info(path) + assert sources[0].origin == OriginType.PATH else: From 55a274fb51821a25f8fc00f695d3d49af66e487c Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Thu, 16 May 2024 16:57:12 +0200 Subject: [PATCH 058/104] fix(iast): empty insecure cookies (#9288) Not report empty cookies vulnerabilities ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. - [x] If change touches code that signs or publishes builds or packages, or handles credentials of any kind, I've requested a review from `@DataDog/security-design-and-guidance`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../_iast/taint_sinks/insecure_cookie.py | 3 + tests/contrib/flask/test_flask_appsec_iast.py | 235 ++++++++++++++++++ 2 files changed, 238 insertions(+) diff --git a/ddtrace/appsec/_iast/taint_sinks/insecure_cookie.py b/ddtrace/appsec/_iast/taint_sinks/insecure_cookie.py index b2e151a666b..f5539e919fb 100644 --- a/ddtrace/appsec/_iast/taint_sinks/insecure_cookie.py +++ b/ddtrace/appsec/_iast/taint_sinks/insecure_cookie.py @@ -36,6 +36,9 @@ def asm_check_cookies(cookies: Optional[Dict[str, str]]) -> None: for cookie_key, cookie_value in cookies.items(): lvalue = cookie_value.lower().replace(" ", "") + # If lvalue starts with ";" means that the cookie is empty, like ';httponly;path=/;samesite=strict' + if lvalue == "" or lvalue.startswith(";"): + continue if ";secure" not in lvalue: increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, InsecureCookie.vulnerability_type) diff --git a/tests/contrib/flask/test_flask_appsec_iast.py b/tests/contrib/flask/test_flask_appsec_iast.py index 702375dcd8c..d197d639fd5 100644 --- a/tests/contrib/flask/test_flask_appsec_iast.py +++ b/tests/contrib/flask/test_flask_appsec_iast.py @@ -8,6 +8,9 @@ from ddtrace.appsec._iast import oce from ddtrace.appsec._iast._utils import _is_python_version_supported as python_supported_by_iast from ddtrace.appsec._iast.constants import VULN_HEADER_INJECTION +from ddtrace.appsec._iast.constants import VULN_INSECURE_COOKIE +from ddtrace.appsec._iast.constants import VULN_NO_HTTPONLY_COOKIE +from ddtrace.appsec._iast.constants import VULN_NO_SAMESITE_COOKIE from ddtrace.appsec._iast.constants import VULN_SQL_INJECTION from ddtrace.appsec._iast.taint_sinks.header_injection import patch as patch_header_injection from ddtrace.contrib.sqlite3.patch import patch as patch_sqlite_sqli @@ -636,6 +639,238 @@ def header_injection(): # assert vulnerability["location"]["line"] == line # assert vulnerability["hash"] == hash_value + @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") + def test_flask_insecure_cookie(self): + @self.app.route("/insecure_cookie/", methods=["GET", "POST"]) + def insecure_cookie(): + from flask import Response + from flask import request + + from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + + tainted_string = request.form.get("name") + assert is_pyobject_tainted(tainted_string) + resp = Response("OK") + resp.set_cookie("insecure", "cookie", secure=False, httponly=True, samesite="Strict") + return resp + + with override_global_config( + dict( + _iast_enabled=True, + _deduplication_enabled=False, + ) + ): + resp = self.client.post("/insecure_cookie/", data={"name": "test"}) + assert resp.status_code == 200 + + root_span = self.pop_spans()[0] + assert root_span.get_metric(IAST.ENABLED) == 1.0 + + loaded = json.loads(root_span.get_tag(IAST.JSON)) + assert loaded["sources"] == [] + assert len(loaded["vulnerabilities"]) == 1 + vulnerability = loaded["vulnerabilities"][0] + assert vulnerability["type"] == VULN_INSECURE_COOKIE + assert vulnerability["evidence"] == {"valueParts": [{"value": "insecure"}]} + assert "path" not in vulnerability["location"].keys() + assert "line" not in vulnerability["location"].keys() + assert vulnerability["location"]["spanId"] + assert vulnerability["hash"] + + @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") + def test_flask_insecure_cookie_empty(self): + @self.app.route("/insecure_cookie_empty/", methods=["GET", "POST"]) + def insecure_cookie_empty(): + from flask import Response + from flask import request + + from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + + tainted_string = request.form.get("name") + assert is_pyobject_tainted(tainted_string) + resp = Response("OK") + resp.set_cookie("insecure", "", secure=False, httponly=True, samesite="Strict") + return resp + + with override_global_config( + dict( + _iast_enabled=True, + _deduplication_enabled=False, + ) + ): + resp = self.client.post("/insecure_cookie_empty/", data={"name": "test"}) + assert resp.status_code == 200 + + root_span = self.pop_spans()[0] + assert root_span.get_metric(IAST.ENABLED) == 1.0 + + loaded = root_span.get_tag(IAST.JSON) + assert loaded is None + + @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") + def test_flask_no_http_only_cookie(self): + @self.app.route("/no_http_only_cookie/", methods=["GET", "POST"]) + def no_http_only_cookie(): + from flask import Response + from flask import request + + from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + + tainted_string = request.form.get("name") + assert is_pyobject_tainted(tainted_string) + resp = Response("OK") + resp.set_cookie("insecure", "cookie", secure=True, httponly=False, samesite="Strict") + return resp + + with override_global_config( + dict( + _iast_enabled=True, + _deduplication_enabled=False, + ) + ): + resp = self.client.post("/no_http_only_cookie/", data={"name": "test"}) + assert resp.status_code == 200 + + root_span = self.pop_spans()[0] + assert root_span.get_metric(IAST.ENABLED) == 1.0 + + loaded = json.loads(root_span.get_tag(IAST.JSON)) + assert loaded["sources"] == [] + assert len(loaded["vulnerabilities"]) == 1 + vulnerability = loaded["vulnerabilities"][0] + assert vulnerability["type"] == VULN_NO_HTTPONLY_COOKIE + assert vulnerability["evidence"] == {"valueParts": [{"value": "insecure"}]} + assert "path" not in vulnerability["location"].keys() + assert "line" not in vulnerability["location"].keys() + assert vulnerability["location"]["spanId"] + assert vulnerability["hash"] + + @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") + def test_flask_no_http_only_cookie_empty(self): + @self.app.route("/no_http_only_cookie_empty/", methods=["GET", "POST"]) + def no_http_only_cookie_empty(): + from flask import Response + from flask import request + + from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + + tainted_string = request.form.get("name") + assert is_pyobject_tainted(tainted_string) + resp = Response("OK") + resp.set_cookie("insecure", "", secure=True, httponly=False, samesite="Strict") + return resp + + with override_global_config( + dict( + _iast_enabled=True, + _deduplication_enabled=False, + ) + ): + resp = self.client.post("/no_http_only_cookie_empty/", data={"name": "test"}) + assert resp.status_code == 200 + + root_span = self.pop_spans()[0] + assert root_span.get_metric(IAST.ENABLED) == 1.0 + + loaded = root_span.get_tag(IAST.JSON) + assert loaded is None + + @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") + def test_flask_no_samesite_cookie(self): + @self.app.route("/no_samesite_cookie/", methods=["GET", "POST"]) + def no_samesite_cookie(): + from flask import Response + from flask import request + + from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + + tainted_string = request.form.get("name") + assert is_pyobject_tainted(tainted_string) + resp = Response("OK") + resp.set_cookie("insecure", "cookie", secure=True, httponly=True, samesite="None") + return resp + + with override_global_config( + dict( + _iast_enabled=True, + _deduplication_enabled=False, + ) + ): + resp = self.client.post("/no_samesite_cookie/", data={"name": "test"}) + assert resp.status_code == 200 + + root_span = self.pop_spans()[0] + assert root_span.get_metric(IAST.ENABLED) == 1.0 + + loaded = json.loads(root_span.get_tag(IAST.JSON)) + assert loaded["sources"] == [] + assert len(loaded["vulnerabilities"]) == 1 + vulnerability = loaded["vulnerabilities"][0] + assert vulnerability["type"] == VULN_NO_SAMESITE_COOKIE + assert vulnerability["evidence"] == {"valueParts": [{"value": "insecure"}]} + assert "path" not in vulnerability["location"].keys() + assert "line" not in vulnerability["location"].keys() + assert vulnerability["location"]["spanId"] + assert vulnerability["hash"] + + @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") + def test_flask_no_samesite_cookie_empty(self): + @self.app.route("/no_samesite_cookie_empty/", methods=["GET", "POST"]) + def no_samesite_cookie_empty(): + from flask import Response + from flask import request + + from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + + tainted_string = request.form.get("name") + assert is_pyobject_tainted(tainted_string) + resp = Response("OK") + resp.set_cookie("insecure", "", secure=True, httponly=True, samesite="None") + return resp + + with override_global_config( + dict( + _iast_enabled=True, + _deduplication_enabled=False, + ) + ): + resp = self.client.post("/no_samesite_cookie_empty/", data={"name": "test"}) + assert resp.status_code == 200 + + root_span = self.pop_spans()[0] + loaded = root_span.get_tag(IAST.JSON) + assert loaded is None + + @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") + def test_flask_cookie_secure(self): + @self.app.route("/cookie_secure/", methods=["GET", "POST"]) + def cookie_secure(): + from flask import Response + from flask import request + + from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted + + tainted_string = request.form.get("name") + assert is_pyobject_tainted(tainted_string) + resp = Response("OK") + resp.set_cookie("insecure", "cookie", secure=True, httponly=True, samesite="Strict") + return resp + + with override_global_config( + dict( + _iast_enabled=True, + _deduplication_enabled=False, + ) + ): + resp = self.client.post("/cookie_secure/", data={"name": "test"}) + assert resp.status_code == 200 + + root_span = self.pop_spans()[0] + assert root_span.get_metric(IAST.ENABLED) == 1.0 + + loaded = root_span.get_tag(IAST.JSON) + assert loaded is None + class FlaskAppSecIASTDisabledTestCase(BaseFlaskTestCase): @pytest.fixture(autouse=True) From 02edac34fa177872b46a54c28268a5ebbf376842 Mon Sep 17 00:00:00 2001 From: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> Date: Thu, 16 May 2024 17:25:45 +0200 Subject: [PATCH 059/104] chore(tracer): hatch.toml as a guild file (#9290) as all teams may want to modify it for the settings of hatch tests, make `hatch.toml` a guild file ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [ ] Title is accurate - [ ] All changes are related to the pull request's stated goal - [ ] Description motivates each change - [ ] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [ ] Testing strategy adequately addresses listed risks - [ ] Change is maintainable (easy to change, telemetry, documentation) - [ ] Release note makes sense to a user of the library - [ ] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [ ] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2214fc4fb52..3cc23a14907 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -105,6 +105,7 @@ tests/internal/remoteconfig @DataDog/remote-config @DataDog/apm-core-pyt setup.py @DataDog/python-guild setup.cfg @DataDog/python-guild pyproject.toml @DataDog/python-guild +hatch.toml @DataDog/python-guild .readthedocs.yml @DataDog/python-guild @DataDog/apm-core-python README.md @DataDog/python-guild @DataDog/apm-core-python mypy.ini @DataDog/python-guild @DataDog/apm-core-python From 74932acec04beb9ecea9987dc9919956788fdbc4 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Thu, 16 May 2024 17:58:29 +0200 Subject: [PATCH 060/104] chore(iast): enable iast in framework tests (#9255) ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. - [x] If change touches code that signs or publishes builds or packages, or handles credentials of any kind, I've requested a review from `@DataDog/security-design-and-guidance`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Co-authored-by: Federico Mon Co-authored-by: Juanjo Alvarez Martinez --- .github/workflows/test_frameworks.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/test_frameworks.yml b/.github/workflows/test_frameworks.yml index 855809ed4d0..8944eaf38fe 100644 --- a/.github/workflows/test_frameworks.yml +++ b/.github/workflows/test_frameworks.yml @@ -30,9 +30,11 @@ jobs: needs: needs-run env: DD_PROFILING_ENABLED: true + DD_IAST_ENABLED: true DD_TESTING_RAISE: true CMAKE_BUILD_PARALLEL_LEVEL: 12 DD_DEBUGGER_EXPL_OUTPUT_FILE: debugger-expl.txt + defaults: run: working-directory: bottle @@ -128,6 +130,7 @@ jobs: name: Django 3.1 (with ${{ matrix.suffix }}) env: DD_PROFILING_ENABLED: true + DD_IAST_ENABLED: true DD_TESTING_RAISE: true DD_DEBUGGER_EXPL_ENCODE: 0 # Disabled to speed up DD_DEBUGGER_EXPL_PROFILER_ENABLED: ${{ matrix.expl_profiler }} @@ -203,6 +206,7 @@ jobs: needs: needs-run env: DD_PROFILING_ENABLED: true + DD_IAST_ENABLED: true DD_TESTING_RAISE: true PYTHONPATH: ../ddtrace/tests/debugging/exploration/:. DD_DEBUGGER_EXPL_OUTPUT_FILE: debugger-expl.txt @@ -251,6 +255,7 @@ jobs: env: DD_TESTING_RAISE: true DD_PROFILING_ENABLED: true + DD_IAST_ENABLED: true CMAKE_BUILD_PARALLEL_LEVEL: 12 DD_DEBUGGER_EXPL_OUTPUT_FILE: debugger-expl.txt defaults: @@ -298,6 +303,7 @@ jobs: TOX_TESTENV_PASSENV: DD_TESTING_RAISE DD_PROFILING_ENABLED DD_TESTING_RAISE: true DD_PROFILING_ENABLED: true + DD_IAST_ENABLED: true PYTHONPATH: ../ddtrace/tests/debugging/exploration/ DD_DEBUGGER_EXPL_OUTPUT_FILE: debugger-expl.txt CMAKE_BUILD_PARALLEL_LEVEL: 12 @@ -389,6 +395,7 @@ jobs: TOX_TESTENV_PASSENV: DD_TESTING_RAISE DD_PROFILING_ENABLED DD_TESTING_RAISE: true DD_PROFILING_ENABLED: true + DD_IAST_ENABLED: true PYTHONPATH: ../ddtrace/tests/debugging/exploration/ DD_DEBUGGER_EXPL_OUTPUT_FILE: debugger-expl.txt CMAKE_BUILD_PARALLEL_LEVEL: 12 @@ -439,6 +446,7 @@ jobs: env: DD_TESTING_RAISE: true DD_PROFILING_ENABLED: true + DD_IAST_ENABLED: true PYTHONPATH: ../ddtrace/tests/debugging/exploration/ DD_DEBUGGER_EXPL_OUTPUT_FILE: debugger-expl.txt CMAKE_BUILD_PARALLEL_LEVEL: 12 @@ -481,6 +489,7 @@ jobs: env: DD_TESTING_RAISE: true DD_PROFILING_ENABLED: true + DD_IAST_ENABLED: true CMAKE_BUILD_PARALLEL_LEVEL: 12 DD_DEBUGGER_EXPL_OUTPUT_FILE: debugger-expl.txt defaults: @@ -524,6 +533,7 @@ jobs: env: DD_TESTING_RAISE: true DD_PROFILING_ENABLED: true + DD_IAST_ENABLED: true CMAKE_BUILD_PARALLEL_LEVEL: 12 defaults: run: @@ -564,6 +574,7 @@ jobs: needs: needs-run env: DD_TESTING_RAISE: true + DD_IAST_ENABLED: true # PYTHONPATH: ../ddtrace/tests/debugging/exploration/ CMAKE_BUILD_PARALLEL_LEVEL: 12 defaults: @@ -599,6 +610,7 @@ jobs: env: DD_TESTING_RAISE: true DD_PROFILING_ENABLED: true + DD_IAST_ENABLED: true PYTHONPATH: ../ddtrace/tests/debugging/exploration/ DD_DEBUGGER_EXPL_OUTPUT_FILE: debugger-expl.txt CMAKE_BUILD_PARALLEL_LEVEL: 12 From d75414ccb19bdaeb97ef137fac7a3871a18c8ef8 Mon Sep 17 00:00:00 2001 From: Emmett Butler <723615+emmettbutler@users.noreply.github.com> Date: Thu, 16 May 2024 09:48:05 -0700 Subject: [PATCH 061/104] chore(cassandra): refactor to separate cassandra logic from tracing logic (#9268) This pull request moves shuffles some things around in the cassandra integration in the interest of clarifying the separation of concerns between cassandra-specific logic and product logic (particularly tracing). This pull request is an incremental step toward that goal, and there will be similar ones to follow. ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Brett Langdon --- ddtrace/contrib/cassandra/session.py | 104 ++++++++++++++++----------- 1 file changed, 62 insertions(+), 42 deletions(-) diff --git a/ddtrace/contrib/cassandra/session.py b/ddtrace/contrib/cassandra/session.py index c8f821ce4a0..06c5e01d495 100644 --- a/ddtrace/contrib/cassandra/session.py +++ b/ddtrace/contrib/cassandra/session.py @@ -2,6 +2,10 @@ Trace queries along a session to a cassandra cluster """ import sys +from typing import Any +from typing import Dict +from typing import List +from typing import Optional from cassandra import __version__ @@ -10,7 +14,12 @@ import cassandra.cluster as cassandra_cluster except AttributeError: from cassandra import cluster as cassandra_cluster +from cassandra.query import BatchStatement +from cassandra.query import BoundStatement +from cassandra.query import PreparedStatement +from cassandra.query import SimpleStatement +from ddtrace import Span from ddtrace import config from ddtrace.internal.constants import COMPONENT @@ -131,7 +140,12 @@ def traced_start_fetching_next_page(func, instance, args, kwargs): query = getattr(instance, "query", None) - span = _start_span_and_set_tags(pin, query, session, cluster) + sanitized_query = _sanitize_query(query) if isinstance(query, BatchStatement) else None + statements_and_parameters = query._statements_and_parameters if isinstance(query, BatchStatement) else None + additional_tags = dict(**_extract_session_metas(session), **_extract_cluster_metas(cluster)) + span = _start_span_and_set_tags( + pin, _get_resource(query), additional_tags, sanitized_query, statements_and_parameters + ) page_number = getattr(instance, PAGE_NUMBER, 1) + 1 setattr(instance, PAGE_NUMBER, page_number) @@ -152,7 +166,12 @@ def traced_execute_async(func, instance, args, kwargs): query = get_argument_value(args, kwargs, 0, "query") - span = _start_span_and_set_tags(pin, query, instance, cluster) + sanitized_query = _sanitize_query(query) if isinstance(query, BatchStatement) else None + statements_and_parameters = query._statements_and_parameters if isinstance(query, BatchStatement) else None + additional_tags = dict(**_extract_session_metas(instance), **_extract_cluster_metas(cluster)) + span = _start_span_and_set_tags( + pin, _get_resource(query), additional_tags, sanitized_query, statements_and_parameters + ) try: result = func(*args, **kwargs) @@ -179,24 +198,29 @@ def traced_execute_async(func, instance, args, kwargs): raise -def _start_span_and_set_tags(pin, query, session, cluster): - service = pin.service - tracer = pin.tracer - span_name = schematize_database_operation("cassandra.query", database_provider="cassandra") - span = tracer.trace(span_name, service=service, span_type=SpanTypes.CASSANDRA) - +def _start_span_and_set_tags( + pin, + resource: str, + additional_tags: Dict, + query: Optional[str] = None, + statements_and_parameters: Optional[List] = None, +) -> Span: + span = pin.tracer.trace( + schematize_database_operation("cassandra.query", database_provider="cassandra"), + service=pin.service, + span_type=SpanTypes.CASSANDRA, + ) span.set_tag_str(COMPONENT, config.cassandra.integration_name) span.set_tag_str(db.SYSTEM, "cassandra") - - # set span.kind to the type of request being performed span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) - span.set_tag(SPAN_MEASURED_KEY) - _sanitize_query(span, query) - span.set_tags(_extract_session_metas(session)) # FIXME[matt] do once? - span.set_tags(_extract_cluster_metas(cluster)) - # set analytics sample rate if enabled + span.set_tags(additional_tags) span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.cassandra.get_analytics_sample_rate()) + if query is not None: + span.set_tag_str("cassandra.query", query) + if statements_and_parameters is not None: + span.set_metric("cassandra.batch_size", len(statements_and_parameters)) + span.resource = resource[:RESOURCE_MAX_LENGTH] return span @@ -261,34 +285,30 @@ def _extract_result_metas(result): return metas -def _sanitize_query(span, query): - # TODO (aaditya): fix this hacky type check. we need it to avoid circular imports - t = type(query).__name__ - - resource = None - if t in ("SimpleStatement", "PreparedStatement"): - # reset query if a string is available - resource = getattr(query, "query_string", query) - elif t == "BatchStatement": - resource = "BatchStatement" - # Each element in `_statements_and_parameters` is: - # (is_prepared, statement, parameters) - # ref:https://github.com/datastax/python-driver/blob/13d6d72be74f40fcef5ec0f2b3e98538b3b87459/cassandra/query.py#L844 - # - # For prepared statements, the `statement` value is just the query_id - # which is not a statement and when trying to join with other strings - # raises an error in python3 around joining bytes to unicode, so this - # just filters out prepared statements from this tag value - q = "; ".join(q[1] for q in query._statements_and_parameters[:2] if not q[0]) - span.set_tag_str("cassandra.query", q) - span.set_metric("cassandra.batch_size", len(query._statements_and_parameters)) - elif t == "BoundStatement": +def _get_resource(query: Any) -> str: + if isinstance(query, SimpleStatement) or isinstance(query, PreparedStatement): + return getattr(query, "query_string", query) + elif isinstance(query, BatchStatement): + return "BatchStatement" + elif isinstance(query, BoundStatement): ps = getattr(query, "prepared_statement", None) if ps: - resource = getattr(ps, "query_string", None) - elif t == "str": - resource = query + return getattr(ps, "query_string", None) + elif isinstance(query, str): + return query else: - resource = "unknown-query-type" # FIXME[matt] what else do to here? + return "unknown-query-type" + + +def _sanitize_query(query: BatchStatement) -> str: + """ + Each element in `_statements_and_parameters` is: + (is_prepared, statement, parameters) + ref:https://github.com/datastax/python-driver/blob/13d6d72be74f40fcef5ec0f2b3e98538b3b87459/cassandra/query.py#L844 - span.resource = str(resource)[:RESOURCE_MAX_LENGTH] + For prepared statements, the `statement` value is just the query_id + which is not a statement and when trying to join with other strings + raises an error in python3 around joining bytes to unicode, so this + just filters out prepared statements from this tag value + """ + return "; ".join(q[1] for q in query._statements_and_parameters[:2] if not q[0]) From 965930aada6681f50379c84fcfd9f93fa7a6cd36 Mon Sep 17 00:00:00 2001 From: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> Date: Thu, 16 May 2024 19:36:57 +0200 Subject: [PATCH 062/104] chore(asm): enable iast for threat tests (#9280) ## Motivation To increase test coverage and problem detection, we need to be able to test several features at once. ## This PR - add, for each threat test suite, a second run with IAST enabled - change the way the threat tests use the tracer instrumentation, by directly using `ddtrace.auto` instead of calling specific patching methods. This makes the whole test suites closer to a real application - fix a small regression in response header report for fastapi that was detected in those new tests - fix a small (unreleased) bug in Django 2.2 not working with IAST - improve `repr` for `interface` fixture to allow more user friendly reports in case of CI failure - relax some header test for api security as the tracer may add additional headers in the response. - fix an (unreleased) bug in exploit prevention blocking mechanism ## Revealed Issues - Exploit Prevention and IAST are not working properly together for now. - Root span is not reported in the span list by the tracer object with recent versions of fastAPI (probably a test only problem) APPSEC-53115 ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Alberto Vara Co-authored-by: Romain Komorn <136473744+romainkomorndatadog@users.noreply.github.com> --- ddtrace/appsec/_common_module_patches.py | 19 +-- .../_iast/taint_sinks/header_injection.py | 6 +- ddtrace/appsec/_processor.py | 6 +- hatch.toml | 9 +- tests/appsec/contrib_appsec/conftest.py | 34 ++-- .../appsec/contrib_appsec/django_app/urls.py | 14 ++ .../appsec/contrib_appsec/fastapi_app/app.py | 16 +- tests/appsec/contrib_appsec/flask_app/app.py | 4 +- tests/appsec/contrib_appsec/test_django.py | 5 +- tests/appsec/contrib_appsec/test_fastapi.py | 20 +-- tests/appsec/contrib_appsec/test_flask.py | 8 +- tests/appsec/contrib_appsec/utils.py | 145 +++++++++--------- 12 files changed, 146 insertions(+), 140 deletions(-) diff --git a/ddtrace/appsec/_common_module_patches.py b/ddtrace/appsec/_common_module_patches.py index 856681e4540..8ed2243957f 100644 --- a/ddtrace/appsec/_common_module_patches.py +++ b/ddtrace/appsec/_common_module_patches.py @@ -8,6 +8,7 @@ from typing import Callable from typing import Dict +from ddtrace.appsec._constants import WAF_ACTIONS from ddtrace.appsec._constants import WAF_CONTEXT_NAMES from ddtrace.internal import core from ddtrace.internal._exceptions import BlockingException @@ -52,7 +53,6 @@ def wrapped_open_CFDDB7ABBA9081B6(original_open_callable, instance, args, kwargs try: from ddtrace.appsec._asm_request_context import call_waf_callback from ddtrace.appsec._asm_request_context import in_context - from ddtrace.appsec._asm_request_context import is_blocked from ddtrace.appsec._constants import EXPLOIT_PREVENTION except ImportError: # open is used during module initialization @@ -65,12 +65,12 @@ def wrapped_open_CFDDB7ABBA9081B6(original_open_callable, instance, args, kwargs except Exception: filename = "" if filename and in_context(): - call_waf_callback( + res = call_waf_callback( {EXPLOIT_PREVENTION.ADDRESS.LFI: filename}, crop_trace="wrapped_open_CFDDB7ABBA9081B6", rule_type=EXPLOIT_PREVENTION.TYPE.LFI, ) - if is_blocked(): + if res and WAF_ACTIONS.BLOCK_ACTION in res.actions: raise BlockingException(core.get_item(WAF_CONTEXT_NAMES.BLOCKED), "exploit_prevention", "lfi", filename) return original_open_callable(*args, **kwargs) @@ -88,7 +88,6 @@ def wrapped_open_ED4CF71136E15EBF(original_open_callable, instance, args, kwargs try: from ddtrace.appsec._asm_request_context import call_waf_callback from ddtrace.appsec._asm_request_context import in_context - from ddtrace.appsec._asm_request_context import is_blocked from ddtrace.appsec._constants import EXPLOIT_PREVENTION except ImportError: # open is used during module initialization @@ -100,12 +99,12 @@ def wrapped_open_ED4CF71136E15EBF(original_open_callable, instance, args, kwargs if url.__class__.__name__ == "Request": url = url.get_full_url() if isinstance(url, str): - call_waf_callback( + res = call_waf_callback( {EXPLOIT_PREVENTION.ADDRESS.SSRF: url}, crop_trace="wrapped_open_ED4CF71136E15EBF", rule_type=EXPLOIT_PREVENTION.TYPE.SSRF, ) - if is_blocked(): + if res and WAF_ACTIONS.BLOCK_ACTION in res.actions: raise BlockingException(core.get_item(WAF_CONTEXT_NAMES.BLOCKED), "exploit_prevention", "ssrf", url) return original_open_callable(*args, **kwargs) @@ -123,7 +122,6 @@ def wrapped_request_D8CB81E472AF98A2(original_request_callable, instance, args, try: from ddtrace.appsec._asm_request_context import call_waf_callback from ddtrace.appsec._asm_request_context import in_context - from ddtrace.appsec._asm_request_context import is_blocked from ddtrace.appsec._constants import EXPLOIT_PREVENTION except ImportError: # open is used during module initialization @@ -133,12 +131,12 @@ def wrapped_request_D8CB81E472AF98A2(original_request_callable, instance, args, url = args[1] if len(args) > 1 else kwargs.get("url", None) if url and in_context(): if isinstance(url, str): - call_waf_callback( + res = call_waf_callback( {EXPLOIT_PREVENTION.ADDRESS.SSRF: url}, crop_trace="wrapped_request_D8CB81E472AF98A2", rule_type=EXPLOIT_PREVENTION.TYPE.SSRF, ) - if is_blocked(): + if res and WAF_ACTIONS.BLOCK_ACTION in res.actions: raise BlockingException(core.get_item(WAF_CONTEXT_NAMES.BLOCKED), "exploit_prevention", "ssrf", url) return original_request_callable(*args, **kwargs) @@ -179,6 +177,9 @@ def apply_patch(parent, attribute, replacement): # Avoid overwriting the original function if we call this twice if not isinstance(current_attribute, FunctionWrapper): _DD_ORIGINAL_ATTRIBUTES[(parent, attribute)] = current_attribute + elif isinstance(replacement, FunctionWrapper): + # Avoid double patching + return setattr(parent, attribute, replacement) except (TypeError, AttributeError): patch_builtins(parent, attribute, replacement) diff --git a/ddtrace/appsec/_iast/taint_sinks/header_injection.py b/ddtrace/appsec/_iast/taint_sinks/header_injection.py index 77f146caf6a..454d31894aa 100644 --- a/ddtrace/appsec/_iast/taint_sinks/header_injection.py +++ b/ddtrace/appsec/_iast/taint_sinks/header_injection.py @@ -47,8 +47,12 @@ def _(m): @when_imported("django.http.response") def _(m): trace_utils.wrap(m, "HttpResponse.__setitem__", _iast_h) - trace_utils.wrap(m, "ResponseHeaders.__setitem__", _iast_h) trace_utils.wrap(m, "HttpResponseBase.__setitem__", _iast_h) + try: + trace_utils.wrap(m, "ResponseHeaders.__setitem__", _iast_h) + except AttributeError: + # no ResponseHeaders in django<3 + pass _set_metric_iast_instrumented_sink(VULN_HEADER_INJECTION) diff --git a/ddtrace/appsec/_processor.py b/ddtrace/appsec/_processor.py index db0b83d6eb4..34aecadb612 100644 --- a/ddtrace/appsec/_processor.py +++ b/ddtrace/appsec/_processor.py @@ -124,9 +124,13 @@ def _set_headers(span: Span, headers: Any, kind: str, only_asm_enabled: bool = F key, value = k else: key, value = k, headers[k] + if isinstance(key, bytes): + key = key.decode() + if isinstance(value, bytes): + value = value.decode() if key.lower() in (_COLLECTED_REQUEST_HEADERS_ASM_ENABLED if only_asm_enabled else _COLLECTED_REQUEST_HEADERS): # since the header value can be a list, use `set_tag()` to ensure it is converted to a string - span.set_tag(_normalize_tag_name(kind, key), value) + (span._local_root or span).set_tag(_normalize_tag_name(kind, key), value) def _get_rate_limiter() -> RateLimiter: diff --git a/hatch.toml b/hatch.toml index 4d821289944..b49b99393a0 100644 --- a/hatch.toml +++ b/hatch.toml @@ -188,7 +188,8 @@ dependencies = [ [envs.appsec_threats_django.scripts] test = [ "pip freeze", - "PYTHONPATH=. python -m pytest tests/appsec/contrib_appsec/test_django.py" + "DD_IAST_ENABLED=false python -m pytest tests/appsec/contrib_appsec/test_django.py", + "DD_IAST_ENABLED=true DD_IAST_REQUEST_SAMPLING=100 python -m pytest tests/appsec/contrib_appsec/test_django.py" ] # python 3.12 should replace 3.11 in this list, but installation is failing on 3.12 @@ -232,7 +233,8 @@ dependencies = [ [envs.appsec_threats_flask.scripts] test = [ "pip freeze", - "PYTHONPATH=. python -m pytest tests/appsec/contrib_appsec/test_flask.py" + "DD_IAST_ENABLED=false python -m pytest tests/appsec/contrib_appsec/test_flask.py", + "DD_IAST_ENABLED=true DD_IAST_REQUEST_SAMPLING=100 python -m pytest tests/appsec/contrib_appsec/test_flask.py" ] # python 3.12 should replace some 3.11 in this list, but installation is failing on 3.12 @@ -273,7 +275,8 @@ dependencies = [ [envs.appsec_threats_fastapi.scripts] test = [ "pip freeze", - "PYTHONPATH=. python -m pytest tests/appsec/contrib_appsec/test_fastapi.py" + "DD_IAST_ENABLED=false python -m pytest tests/appsec/contrib_appsec/test_fastapi.py", + "DD_IAST_ENABLED=true DD_IAST_REQUEST_SAMPLING=100 python -m pytest tests/appsec/contrib_appsec/test_fastapi.py" ] # python 3.12 should replace some 3.11 in this list, but installation is failing on 3.12 diff --git a/tests/appsec/contrib_appsec/conftest.py b/tests/appsec/contrib_appsec/conftest.py index 356c2fced5c..94efed1dd43 100644 --- a/tests/appsec/contrib_appsec/conftest.py +++ b/tests/appsec/contrib_appsec/conftest.py @@ -1,30 +1,16 @@ -import unittest.mock +import ddtrace.auto # noqa: F401 -import pytest -from ddtrace.settings.asm import config as asm_config -from tests.utils import TracerSpanContainer -from tests.utils import _build_tree +# ensure the tracer is loaded and started first for possible iast patching +print(f"ddtrace version {ddtrace.version.get_version()}") +import unittest.mock # noqa: E402 -@pytest.fixture -def iast(): - from os import environ - - from ddtrace import config - from ddtrace.appsec._iast import oce - from ddtrace.appsec._iast._patch_modules import patch_iast - - environ["DD_IAST_ENABLED"] = "true" - - asm_config._iast_enabled = True +import pytest # noqa: E402 - config._raise = True - - oce._enabled = True - - patch_iast() - yield +from ddtrace.settings.asm import config as asm_config # noqa: E402 +from tests.utils import TracerSpanContainer # noqa: E402 +from tests.utils import _build_tree # noqa: E402 @pytest.fixture @@ -42,6 +28,10 @@ def get_root_span(): for span in test_spans.spans: if span.parent_id is None: return _build_tree(test_spans.spans, span) + # In case root span is not found, try to find a span with a local root + for span in test_spans.spans: + if span._local_root is not None: + return _build_tree(test_spans.spans, span._local_root) yield get_root_span diff --git a/tests/appsec/contrib_appsec/django_app/urls.py b/tests/appsec/contrib_appsec/django_app/urls.py index a297f18fab3..0baaac988d9 100644 --- a/tests/appsec/contrib_appsec/django_app/urls.py +++ b/tests/appsec/contrib_appsec/django_app/urls.py @@ -101,6 +101,20 @@ def rasp(request, endpoint: str): res.append(f"Error: {e}") tracer.current_span()._local_root.set_tag("rasp.request.done", endpoint) return HttpResponse("<\\br>\n".join(res)) + elif endpoint == "shell": + res = ["shell endpoint"] + for param in query_params: + if param.startswith("cmd"): + cmd = query_params[param] + try: + import subprocess + + with subprocess.Popen(cmd, stdout=subprocess.PIPE) as f: + res.append(f"cmd stdout: {f.stdout.read()}") + except Exception as e: + res.append(f"Error: {e}") + tracer.current_span()._local_root.set_tag("rasp.request.done", endpoint) + return HttpResponse("<\\br>\n".join(res)) tracer.current_span()._local_root.set_tag("rasp.request.done", endpoint) return HttpResponse(f"Unknown endpoint: {endpoint}") diff --git a/tests/appsec/contrib_appsec/fastapi_app/app.py b/tests/appsec/contrib_appsec/fastapi_app/app.py index 5111fb6a218..8eac3cca273 100644 --- a/tests/appsec/contrib_appsec/fastapi_app/app.py +++ b/tests/appsec/contrib_appsec/fastapi_app/app.py @@ -117,7 +117,7 @@ async def stream(): @app.post("/rasp/{endpoint:str}/") @app.options("/rasp/{endpoint:str}/") async def rasp(endpoint: str, request: Request): - query_params = dict(request.query_params) + query_params = request.query_params if endpoint == "lfi": res = ["lfi endpoint"] for param in query_params: @@ -158,6 +158,20 @@ async def rasp(endpoint: str, request: Request): res.append(f"Error: {e}") tracer.current_span()._local_root.set_tag("rasp.request.done", endpoint) return HTMLResponse("<\\br>\n".join(res)) + elif endpoint == "shell": + res = ["shell endpoint"] + for param in query_params: + if param.startswith("cmd"): + cmd = query_params[param] + try: + import subprocess + + with subprocess.Popen(cmd, stdout=subprocess.PIPE) as f: + res.append(f"cmd stdout: {f.stdout.read()}") + except Exception as e: + res.append(f"Error: {e}") + tracer.current_span()._local_root.set_tag("rasp.request.done", endpoint) + return HTMLResponse("<\\br>\n".join(res)) tracer.current_span()._local_root.set_tag("rasp.request.done", endpoint) return HTMLResponse(f"Unknown endpoint: {endpoint}") diff --git a/tests/appsec/contrib_appsec/flask_app/app.py b/tests/appsec/contrib_appsec/flask_app/app.py index 8997c3fa0e6..6e32951f79c 100644 --- a/tests/appsec/contrib_appsec/flask_app/app.py +++ b/tests/appsec/contrib_appsec/flask_app/app.py @@ -4,6 +4,8 @@ from flask import request from ddtrace import tracer + +# from ddtrace.appsec.iast import ddtrace_iast_flask_patch import ddtrace.constants from tests.webclient import PingFilter @@ -61,7 +63,7 @@ def new_service(service_name: str): @app.route("/rasp//", methods=["GET", "POST", "OPTIONS"]) def rasp(endpoint: str): - query_params = request.args.to_dict() + query_params = request.args if endpoint == "lfi": res = ["lfi endpoint"] for param in query_params: diff --git a/tests/appsec/contrib_appsec/test_django.py b/tests/appsec/contrib_appsec/test_django.py index 663f971b770..f1b1fd35fa4 100644 --- a/tests/appsec/contrib_appsec/test_django.py +++ b/tests/appsec/contrib_appsec/test_django.py @@ -5,7 +5,6 @@ from django.test.client import Client import pytest -from ddtrace.contrib.django import patch from ddtrace.propagation._utils import get_wsgi_header from tests.appsec.contrib_appsec import utils @@ -16,7 +15,6 @@ def interface(self, printer): os.environ["DJANGO_SETTINGS_MODULE"] = "tests.appsec.contrib_appsec.django_app.settings" settings.DEBUG = False django.setup() - patch() client = Client( f"http://localhost:{self.SERVER_PORT}", SERVER_NAME=f"localhost:{self.SERVER_PORT}", @@ -60,13 +58,12 @@ def patch_post(*args, **kwargs): client.post = patch_post interface = utils.Interface("django", django, client) + interface.version = django.VERSION with utils.test_tracer() as tracer: interface.tracer = tracer interface.printer = printer with utils.post_tracer(interface): yield interface - # unpatch failing in this case - # unpatch() def status(self, response): return response.status_code diff --git a/tests/appsec/contrib_appsec/test_fastapi.py b/tests/appsec/contrib_appsec/test_fastapi.py index 357b63f9905..027013eb8ca 100644 --- a/tests/appsec/contrib_appsec/test_fastapi.py +++ b/tests/appsec/contrib_appsec/test_fastapi.py @@ -3,9 +3,6 @@ import pytest import starlette -import ddtrace -from ddtrace.contrib.fastapi import patch as fastapi_patch -from ddtrace.contrib.fastapi import unpatch as fastapi_unpatch from tests.appsec.contrib_appsec import utils from tests.appsec.contrib_appsec.fastapi_app.app import get_app @@ -21,19 +18,12 @@ class Test_FastAPI(utils.Contrib_TestClass_For_Threats): def interface(self, tracer, printer): from fastapi.testclient import TestClient - fastapi_patch() # for fastapi, test tracer needs to be set before the app is created # contrary to other frameworks with utils.test_tracer() as tracer: application = get_app() - @application.middleware("http") - async def traced_middlware(request, call_next): - with ddtrace.tracer.trace("traced_middlware"): - response = await call_next(request) - return response - - client = TestClient(get_app(), base_url="http://localhost:%d" % self.SERVER_PORT) + client = TestClient(application, base_url="http://localhost:%d" % self.SERVER_PORT) def parse_arguments(*args, **kwargs): if "content_type" in kwargs: @@ -74,13 +64,11 @@ def patch_get(*args, **kwargs): client.get = patch_get interface = utils.Interface("fastapi", fastapi, client) + interface.version = FASTAPI_VERSION interface.tracer = tracer interface.printer = printer - try: - with utils.post_tracer(interface): - yield interface - finally: - fastapi_unpatch() + with utils.post_tracer(interface): + yield interface def status(self, response): return response.status_code diff --git a/tests/appsec/contrib_appsec/test_flask.py b/tests/appsec/contrib_appsec/test_flask.py index 68479c66e93..90a35ac0c88 100644 --- a/tests/appsec/contrib_appsec/test_flask.py +++ b/tests/appsec/contrib_appsec/test_flask.py @@ -2,8 +2,6 @@ import pytest from ddtrace import Pin -from ddtrace.contrib.flask import patch -from ddtrace.contrib.flask import unpatch from ddtrace.internal.packages import get_version_for_package from tests.appsec.contrib_appsec import utils from tests.utils import TracerTestCase @@ -34,9 +32,6 @@ def open(self, *args, **kwargs): class BaseFlaskTestCase(TracerTestCase): def setUp(self): super(BaseFlaskTestCase, self).setUp() - - patch() - from tests.appsec.contrib_appsec.flask_app.app import app self.app = app @@ -46,8 +41,6 @@ def setUp(self): def tearDown(self): super(BaseFlaskTestCase, self).tearDown() - # Unpatch Flask - unpatch() class Test_Flask(utils.Contrib_TestClass_For_Threats): @@ -58,6 +51,7 @@ def interface(self, printer): bftc.setUp() bftc.app.config["SERVER_NAME"] = f"localhost:{self.SERVER_PORT}" interface = utils.Interface("flask", bftc.app, bftc.client) + interface.version = FLASK_VERSION initial_get = bftc.client.get diff --git a/tests/appsec/contrib_appsec/utils.py b/tests/appsec/contrib_appsec/utils.py index 850c3d91fa9..ddcb3cafee2 100644 --- a/tests/appsec/contrib_appsec/utils.py +++ b/tests/appsec/contrib_appsec/utils.py @@ -1,6 +1,7 @@ from contextlib import contextmanager import itertools import json +import sys from typing import Dict from typing import List from urllib.parse import quote @@ -26,6 +27,9 @@ def __init__(self, name, framework, client): self.framework = framework self.client = client + def __repr__(self): + return f"Interface({self.name}[{self.version}] Python[{sys.version}])" + def payload_to_xml(payload: Dict[str, str]) -> str: return "".join(f"<{k}>{v}" for k, v in payload.items()) @@ -101,6 +105,7 @@ def test_healthcheck(self, interface: Interface, get_tag, asm_enabled: bool): # if interface.name == "fastapi": # raise pytest.skip("fastapi does not have a healthcheck endpoint") with override_global_config(dict(_asm_enabled=asm_enabled)): + self.update_tracer(interface) response = interface.client.get("/") assert self.status(response) == 200, "healthcheck failed" assert self.body(response) == "ok ASM" @@ -973,7 +978,12 @@ def test_api_security_schemas( if name == "_dd.appsec.s.res.body" and blocked: assert api == [{"errors": [[[{"detail": [8], "title": [8]}]], {"len": 1}]}] else: - assert api in expected_value, (api, name) + assert any( + all(api[0].get(k) == v for k, v in expected[0].items()) for expected in expected_value + ), ( + api, + name, + ) else: assert value is None, name @@ -1212,90 +1222,74 @@ def test_exploit_prevention( rule_file, blocking, ): + if asm_config._iast_enabled: + raise pytest.xfail("iast and exploit prevention not working yet together") from unittest.mock import patch as mock_patch - from ddtrace.appsec._common_module_patches import patch_common_modules - from ddtrace.appsec._common_module_patches import unpatch_common_modules from ddtrace.appsec._constants import APPSEC from ddtrace.appsec._metrics import DDWAF_VERSION - from ddtrace.contrib.requests import patch as patch_requests - from ddtrace.contrib.requests import unpatch as unpatch_requests from ddtrace.ext import http - try: - patch_requests() - with override_global_config(dict(_asm_enabled=asm_enabled, _ep_enabled=ep_enabled)), override_env( - dict(DD_APPSEC_RULES=rule_file) - ), mock_patch("ddtrace.internal.telemetry.metrics_namespaces.MetricNamespace.add_metric") as mocked: - patch_common_modules() - self.update_tracer(interface) - response = interface.client.get(f"/rasp/{endpoint}/?{parameters}") - code = 403 if blocking and asm_enabled and ep_enabled else 200 - assert self.status(response) == code - assert get_tag(http.STATUS_CODE) == str(code) - if code == 200: - assert self.body(response).startswith(f"{endpoint} endpoint") - if asm_enabled and ep_enabled: - self.check_rules_triggered([rule] * (1 if blocking else 2), root_span) - assert self.check_for_stack_trace(root_span) - for trace in self.check_for_stack_trace(root_span): - assert "frames" in trace - function = trace["frames"][0]["function"] - assert any( - function.endswith(top_function) for top_function in top_functions - ), f"unknown top function {function}" - # assert mocked.call_args_list == [] - telemetry_calls = { - (c.__name__, f"{ns}.{nm}", t): v for (c, ns, nm, v, t), _ in mocked.call_args_list - } - assert ( - "CountMetric", - "appsec.rasp.rule.match", - (("rule_type", endpoint), ("waf_version", DDWAF_VERSION)), - ) in telemetry_calls - assert ( - "CountMetric", - "appsec.rasp.rule.eval", - (("rule_type", endpoint), ("waf_version", DDWAF_VERSION)), - ) in telemetry_calls - if blocking: - assert get_tag("rasp.request.done") is None - else: - assert get_tag("rasp.request.done") == endpoint - assert get_metric(APPSEC.RASP_DURATION) is not None - assert get_metric(APPSEC.RASP_DURATION_EXT) is not None - assert get_metric(APPSEC.RASP_RULE_EVAL) is not None - assert float(get_metric(APPSEC.RASP_DURATION_EXT)) >= float(get_metric(APPSEC.RASP_DURATION)) - assert int(get_metric(APPSEC.RASP_RULE_EVAL)) > 0 + with override_global_config(dict(_asm_enabled=asm_enabled, _ep_enabled=ep_enabled)), override_env( + dict(DD_APPSEC_RULES=rule_file) + ), mock_patch("ddtrace.internal.telemetry.metrics_namespaces.MetricNamespace.add_metric") as mocked: + self.update_tracer(interface) + response = interface.client.get(f"/rasp/{endpoint}/?{parameters}") + code = 403 if blocking and asm_enabled and ep_enabled else 200 + assert self.status(response) == code, (self.status(response), code) + assert get_tag(http.STATUS_CODE) == str(code), (get_tag(http.STATUS_CODE), code) + if code == 200: + assert self.body(response).startswith(f"{endpoint} endpoint") + if asm_enabled and ep_enabled: + self.check_rules_triggered([rule] * (1 if blocking else 2), root_span) + assert self.check_for_stack_trace(root_span) + for trace in self.check_for_stack_trace(root_span): + assert "frames" in trace + function = trace["frames"][0]["function"] + assert any( + function.endswith(top_function) for top_function in top_functions + ), f"unknown top function {function}" + # assert mocked.call_args_list == [] + telemetry_calls = {(c.__name__, f"{ns}.{nm}", t): v for (c, ns, nm, v, t), _ in mocked.call_args_list} + assert ( + "CountMetric", + "appsec.rasp.rule.match", + (("rule_type", endpoint), ("waf_version", DDWAF_VERSION)), + ) in telemetry_calls + assert ( + "CountMetric", + "appsec.rasp.rule.eval", + (("rule_type", endpoint), ("waf_version", DDWAF_VERSION)), + ) in telemetry_calls + if blocking: + assert get_tag("rasp.request.done") is None else: - assert get_triggers(root_span()) is None - assert self.check_for_stack_trace(root_span) == [] assert get_tag("rasp.request.done") == endpoint - finally: - unpatch_common_modules() - unpatch_requests() + assert get_metric(APPSEC.RASP_DURATION) is not None + assert get_metric(APPSEC.RASP_DURATION_EXT) is not None + assert get_metric(APPSEC.RASP_RULE_EVAL) is not None + assert float(get_metric(APPSEC.RASP_DURATION_EXT)) >= float(get_metric(APPSEC.RASP_DURATION)) + assert int(get_metric(APPSEC.RASP_RULE_EVAL)) > 0 + else: + assert get_triggers(root_span()) is None + assert self.check_for_stack_trace(root_span) == [] + assert get_tag("rasp.request.done") == endpoint - @pytest.mark.skip(reason="iast integration not working yet") - def test_iast(self, iast, interface, root_span, get_tag): - from ddtrace.appsec._iast.taint_sinks.command_injection import patch - from ddtrace.appsec._iast.taint_sinks.command_injection import unpatch + def test_iast(self, interface, root_span, get_tag): + if interface.name == "fastapi" and asm_config._iast_enabled: + raise pytest.xfail("fastapi does not fully support IAST for now") from ddtrace.ext import http url = "/rasp/shell/?cmd=ls" - try: - patch() - # patch_common_modules() - with override_global_config(dict(_iast_enabled=True)): - self.update_tracer(interface) - response = interface.client.get(url) - assert self.status(response) == 200 - assert get_tag(http.STATUS_CODE) == "200" - assert self.body(response).startswith("shell endpoint") - assert get_tag("_dd.iast.json") - finally: - assert iast is None - unpatch() - # unpatch_common_modules() + self.update_tracer(interface) + response = interface.client.get(url) + assert self.status(response) == 200 + assert get_tag(http.STATUS_CODE) == "200" + assert self.body(response).startswith("shell endpoint") + if asm_config._iast_enabled: + assert get_tag("_dd.iast.json") is not None + else: + assert get_tag("_dd.iast.json") is None @contextmanager @@ -1313,7 +1307,8 @@ def test_tracer(): @contextmanager def post_tracer(interface): - original_tracer = ddtrace.Pin.get_from(interface.framework).tracer + original_tracer = getattr(ddtrace.Pin.get_from(interface.framework), "tracer", None) ddtrace.Pin.override(interface.framework, tracer=interface.tracer) yield - ddtrace.Pin.override(interface.framework, tracer=original_tracer) + if original_tracer is not None: + ddtrace.Pin.override(interface.framework, tracer=original_tracer) From 05fb6fba399ceaa0762304d84869b46144eb0c1a Mon Sep 17 00:00:00 2001 From: Federico Mon Date: Fri, 17 May 2024 08:43:46 +0200 Subject: [PATCH 063/104] chore(iast): ast Visitor as singleton (#9286) IAST: Performance improvement: Reuse AST Visitor object. ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Alberto Vara --- ddtrace/appsec/_iast/_ast/ast_patching.py | 12 ++++++------ ddtrace/appsec/_iast/_ast/visitor.py | 19 +++++++++++++------ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/ddtrace/appsec/_iast/_ast/ast_patching.py b/ddtrace/appsec/_iast/_ast/ast_patching.py index 721fbf9a7bd..2ff3d8027e2 100644 --- a/ddtrace/appsec/_iast/_ast/ast_patching.py +++ b/ddtrace/appsec/_iast/_ast/ast_patching.py @@ -19,6 +19,9 @@ from .visitor import AstVisitor +_VISITOR = AstVisitor() + + # Prefixes for modules where IAST patching is allowed IAST_ALLOWLIST: Tuple[Text, ...] = ("tests.appsec.iast",) IAST_DENYLIST: Tuple[Text, ...] = ( @@ -108,13 +111,10 @@ def visit_ast( ) -> Optional[str]: parsed_ast = ast.parse(source_text, module_path) - visitor = AstVisitor( - filename=module_path, - module_name=module_name, - ) - modified_ast = visitor.visit(parsed_ast) + _VISITOR.update_location(filename=module_path, module_name=module_name) + modified_ast = _VISITOR.visit(parsed_ast) - if not visitor.ast_modified: + if not _VISITOR.ast_modified: return None ast.fix_missing_locations(modified_ast) diff --git a/ddtrace/appsec/_iast/_ast/visitor.py b/ddtrace/appsec/_iast/_ast/visitor.py index 086c14ce80c..3664eb0f16d 100644 --- a/ddtrace/appsec/_iast/_ast/visitor.py +++ b/ddtrace/appsec/_iast/_ast/visitor.py @@ -6,9 +6,11 @@ import os import sys from typing import Any +from typing import Dict # noqa:F401 from typing import List from typing import Set from typing import Text +from typing import Tuple # noqa:F401 from .._metrics import _set_metric_iast_instrumented_propagation from ..constants import DEFAULT_PATH_TRAVERSAL_FUNCTIONS @@ -34,7 +36,7 @@ def _mark_avoid_convert_recursively(node): _mark_avoid_convert_recursively(child) -_ASPECTS_SPEC = { +_ASPECTS_SPEC: Dict[Text, Any] = { "definitions_module": "ddtrace.appsec._iast._taint_tracking.aspects", "alias_module": "ddtrace_aspects", "functions": { @@ -133,7 +135,7 @@ def _mark_avoid_convert_recursively(node): _ASPECTS_SPEC["module_functions"]["os.path"]["splitroot"] = "ddtrace_aspects._aspect_ospathsplitroot" if sys.version_info >= (3, 12) or os.name == "nt": - _ASPECTS_SPEC["module_functions"]["os.path"]["splitdrive"] = "ddtrace_aspects._aspect_ospathsplitdrive" # type: ignore[index] + _ASPECTS_SPEC["module_functions"]["os.path"]["splitdrive"] = "ddtrace_aspects._aspect_ospathsplitdrive" class AstVisitor(ast.NodeTransformer): @@ -151,9 +153,6 @@ def __init__( }, } self._sinkpoints_functions = self._sinkpoints_spec["functions"] - self.ast_modified = False - self.filename = filename - self.module_name = module_name self._aspect_index = _ASPECTS_SPEC["slices"]["index"] self._aspect_slice = _ASPECTS_SPEC["slices"]["slice"] @@ -163,7 +162,6 @@ def __init__( self._aspect_modules = _ASPECTS_SPEC["module_functions"] self._aspect_format_value = _ASPECTS_SPEC["operators"]["FORMAT_VALUE"] self._aspect_build_string = _ASPECTS_SPEC["operators"]["BUILD_STRING"] - self.excluded_functions = _ASPECTS_SPEC["excluded_from_patching"].get(self.module_name, {}) # Sink points self._taint_sink_replace_any = self._merge_taint_sinks( @@ -173,6 +171,15 @@ def __init__( ) self._taint_sink_replace_disabled = _ASPECTS_SPEC["taint_sinks"]["disabled"] + self.update_location(filename, module_name) + + def update_location(self, filename: str = "", module_name: str = ""): + self.filename = filename + self.module_name = module_name + self.ast_modified = False + + excluded_from_patching: Dict[str, Dict[str, Tuple[str]]] = _ASPECTS_SPEC["excluded_from_patching"] + self.excluded_functions = excluded_from_patching.get(self.module_name, {}) self.dont_patch_these_functionsdefs = set() for _, v in self.excluded_functions.items(): if v: From 67741c0ea932bb57bd82b32fe8a6c311e9b56c4c Mon Sep 17 00:00:00 2001 From: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> Date: Fri, 17 May 2024 10:20:48 +0200 Subject: [PATCH 064/104] chore(asm): enable rasp with iast (#9300) - remove delayed patching for rasp/iast as now rasp patching is always lazy. - enable rasp threat tests with iast APPSEC-51853 ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/appsec/_common_module_patches.py | 10 +--------- ddtrace/appsec/_iast/taint_sinks/command_injection.py | 4 ---- tests/appsec/contrib_appsec/utils.py | 6 ++---- 3 files changed, 3 insertions(+), 17 deletions(-) diff --git a/ddtrace/appsec/_common_module_patches.py b/ddtrace/appsec/_common_module_patches.py index 8ed2243957f..e077e62b54e 100644 --- a/ddtrace/appsec/_common_module_patches.py +++ b/ddtrace/appsec/_common_module_patches.py @@ -25,15 +25,7 @@ def patch_common_modules(): try_wrap_function_wrapper("builtins", "open", wrapped_open_CFDDB7ABBA9081B6) - - # due to incompatibilities with gevent, delay the patching if IAST is enabled - if asm_config._iast_enabled: - core.on( - "exploit.prevention.ssrf.patch.urllib", - lambda: try_wrap_function_wrapper("urllib.request", "OpenerDirector.open", wrapped_open_ED4CF71136E15EBF), - ) - else: - try_wrap_function_wrapper("urllib.request", "OpenerDirector.open", wrapped_open_ED4CF71136E15EBF) + try_wrap_function_wrapper("urllib.request", "OpenerDirector.open", wrapped_open_ED4CF71136E15EBF) def unpatch_common_modules(): diff --git a/ddtrace/appsec/_iast/taint_sinks/command_injection.py b/ddtrace/appsec/_iast/taint_sinks/command_injection.py index ad98fd6286b..6b2eb8e7244 100644 --- a/ddtrace/appsec/_iast/taint_sinks/command_injection.py +++ b/ddtrace/appsec/_iast/taint_sinks/command_injection.py @@ -4,7 +4,6 @@ from typing import Union from ddtrace.contrib import trace_utils -from ddtrace.internal import core from ddtrace.internal.logger import get_logger from ddtrace.settings.asm import config as asm_config @@ -42,9 +41,6 @@ def patch(): _set_metric_iast_instrumented_sink(VULN_CMDI) - if asm_config._ep_enabled: - core.dispatch("exploit.prevention.ssrf.patch.urllib") - def unpatch() -> None: trace_utils.unwrap(os, "system") diff --git a/tests/appsec/contrib_appsec/utils.py b/tests/appsec/contrib_appsec/utils.py index ddcb3cafee2..6ec839743d2 100644 --- a/tests/appsec/contrib_appsec/utils.py +++ b/tests/appsec/contrib_appsec/utils.py @@ -1222,8 +1222,6 @@ def test_exploit_prevention( rule_file, blocking, ): - if asm_config._iast_enabled: - raise pytest.xfail("iast and exploit prevention not working yet together") from unittest.mock import patch as mock_patch from ddtrace.appsec._constants import APPSEC @@ -1246,8 +1244,8 @@ def test_exploit_prevention( for trace in self.check_for_stack_trace(root_span): assert "frames" in trace function = trace["frames"][0]["function"] - assert any( - function.endswith(top_function) for top_function in top_functions + assert any(function.endswith(top_function) for top_function in top_functions) or ( + asm_config._iast_enabled and function.endswith("ast_function") ), f"unknown top function {function}" # assert mocked.call_args_list == [] telemetry_calls = {(c.__name__, f"{ns}.{nm}", t): v for (c, ns, nm, v, t), _ in mocked.call_args_list} From 370b175d40c4afaf6145eeae4a81aa97ac16789b Mon Sep 17 00:00:00 2001 From: Romain Komorn <136473744+romainkomorndatadog@users.noreply.github.com> Date: Fri, 17 May 2024 10:33:56 +0100 Subject: [PATCH 065/104] ci: add ITR unskippable marker is subprocess marker is used (#9261) This works around an issue where tests are improperly being skipped due to issues collecting coverage data using coverage.py when subprocess is used. Whenever the subprocess marker is seen during collection, we add the same skipif marker that's used to mark tests as unskippable, which makes pytest run the tests no matter what. This ends up with tests being forced to run and the `test.itr.unskippable` tag being set to `true`: ![image](https://github.com/DataDog/dd-trace-py/assets/136473744/e189cacf-dbf5-4070-80c7-9584afe7edcc) ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- tests/conftest.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index e2f04d18b21..9a553821deb 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -257,6 +257,18 @@ def _subprocess_wrapper(): return _subprocess_wrapper() +@pytest.hookimpl(tryfirst=True) +def pytest_collection_modifyitems(session, config, items): + """Don't let ITR skip tests that use the subprocess marker because coverage collection in subprocesses is broken""" + for item in items: + if item.get_closest_marker("subprocess"): + if item.get_closest_marker("skipif"): + # Respect any existing skipif marker because they preempt ITR's decision-making + continue + unskippable = pytest.mark.skipif(False, reason="datadog_itr_unskippable") + item.add_marker(unskippable) + + @pytest.hookimpl(tryfirst=True) def pytest_runtest_protocol(item): if item.get_closest_marker("skip"): From 39897ac8ab632ab402a50a6433a8b3d73dce24e7 Mon Sep 17 00:00:00 2001 From: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> Date: Fri, 17 May 2024 16:25:51 +0200 Subject: [PATCH 066/104] chore(asm): remove small flakyness in api security test (#9306) Add more timeout (50 seconds) for schema computation test for api security test. The hypothesis fuzzer could create large input and the CI could lag at the same time. This ensures the waf doesn't run out of time. ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- tests/appsec/appsec/api_security/test_schema_fuzz.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/appsec/appsec/api_security/test_schema_fuzz.py b/tests/appsec/appsec/api_security/test_schema_fuzz.py index 2116e94eec5..24cb2f4e5db 100644 --- a/tests/appsec/appsec/api_security/test_schema_fuzz.py +++ b/tests/appsec/appsec/api_security/test_schema_fuzz.py @@ -15,7 +15,9 @@ def build_schema(obj): waf = ddwaf.DDWaf(rules, b"", b"") ctx = waf._at_request_start() res = waf.run( - ctx, {"server.request.body": obj, constants.WAF_DATA_NAMES.PROCESSOR_SETTINGS: {"extract-schema": True}} + ctx, + {"server.request.body": obj, constants.WAF_DATA_NAMES.PROCESSOR_SETTINGS: {"extract-schema": True}}, + timeout_ms=50_000.0, ).derivatives return res["_dd.appsec.s.req.body"] From 3b08d60f9516b5bbb2613abab61d4e12e7d3920e Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Fri, 17 May 2024 18:30:08 +0200 Subject: [PATCH 067/104] fix(asm): enabling builtins.open sink point (#9307) Revert https://github.com/DataDog/dd-trace-py/pull/9247 and restore path traversal patching but this time, instead to patch `open` by AST, we're monkey patching like we're doing with Exploit Prevention ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. - [x] If change touches code that signs or publishes builds or packages, or handles credentials of any kind, I've requested a review from `@DataDog/security-design-and-guidance`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/appsec/_common_module_patches.py | 9 +++- ddtrace/appsec/_iast/_ast/visitor.py | 5 +-- ddtrace/appsec/_iast/_patch_modules.py | 1 - ddtrace/appsec/_iast/taint_sinks/__init__.py | 2 - .../_iast/taint_sinks/path_traversal.py | 29 ++---------- tests/appsec/iast/conftest.py | 6 ++- .../iast/taint_sinks/test_path_traversal.py | 44 +++++++++++++++---- .../appsec/iast/test_iast_propagation_path.py | 12 ----- tests/appsec/iast/test_telemetry.py | 8 ++-- 9 files changed, 55 insertions(+), 61 deletions(-) diff --git a/ddtrace/appsec/_common_module_patches.py b/ddtrace/appsec/_common_module_patches.py index e077e62b54e..effd8473011 100644 --- a/ddtrace/appsec/_common_module_patches.py +++ b/ddtrace/appsec/_common_module_patches.py @@ -10,6 +10,8 @@ from ddtrace.appsec._constants import WAF_ACTIONS from ddtrace.appsec._constants import WAF_CONTEXT_NAMES +from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_sink +from ddtrace.appsec._iast.constants import VULN_PATH_TRAVERSAL from ddtrace.internal import core from ddtrace.internal._exceptions import BlockingException from ddtrace.internal.logger import get_logger @@ -26,6 +28,8 @@ def patch_common_modules(): try_wrap_function_wrapper("builtins", "open", wrapped_open_CFDDB7ABBA9081B6) try_wrap_function_wrapper("urllib.request", "OpenerDirector.open", wrapped_open_ED4CF71136E15EBF) + if asm_config._iast_enabled: + _set_metric_iast_instrumented_sink(VULN_PATH_TRAVERSAL) def unpatch_common_modules(): @@ -38,8 +42,9 @@ def wrapped_open_CFDDB7ABBA9081B6(original_open_callable, instance, args, kwargs wrapper for open file function """ if asm_config._iast_enabled: - # LFI sink to be added - pass + from ddtrace.appsec._iast.taint_sinks.path_traversal import check_and_report_path_traversal + + check_and_report_path_traversal(*args, **kwargs) if asm_config._asm_enabled and asm_config._ep_enabled: try: diff --git a/ddtrace/appsec/_iast/_ast/visitor.py b/ddtrace/appsec/_iast/_ast/visitor.py index 3664eb0f16d..534e721abdf 100644 --- a/ddtrace/appsec/_iast/_ast/visitor.py +++ b/ddtrace/appsec/_iast/_ast/visitor.py @@ -147,10 +147,7 @@ def __init__( self._sinkpoints_spec = { "definitions_module": "ddtrace.appsec._iast.taint_sinks", "alias_module": "ddtrace_taint_sinks", - "functions": { - # FIXME: disabled to unblock release 2.9 - # "open": "ddtrace_taint_sinks.open_path_traversal", - }, + "functions": {}, } self._sinkpoints_functions = self._sinkpoints_spec["functions"] diff --git a/ddtrace/appsec/_iast/_patch_modules.py b/ddtrace/appsec/_iast/_patch_modules.py index 5c38f3d0f62..3d7e1b4f50a 100644 --- a/ddtrace/appsec/_iast/_patch_modules.py +++ b/ddtrace/appsec/_iast/_patch_modules.py @@ -4,7 +4,6 @@ IAST_PATCH = { "command_injection": True, "header_injection": True, - "path_traversal": True, "weak_cipher": True, "weak_hash": True, } diff --git a/ddtrace/appsec/_iast/taint_sinks/__init__.py b/ddtrace/appsec/_iast/taint_sinks/__init__.py index e7c8787c943..18196607fc8 100644 --- a/ddtrace/appsec/_iast/taint_sinks/__init__.py +++ b/ddtrace/appsec/_iast/taint_sinks/__init__.py @@ -1,8 +1,6 @@ from .ast_taint import ast_function -from .path_traversal import open_path_traversal __all__ = [ - "open_path_traversal", "ast_function", ] diff --git a/ddtrace/appsec/_iast/taint_sinks/path_traversal.py b/ddtrace/appsec/_iast/taint_sinks/path_traversal.py index 1c51eb273cf..25750b3bb5e 100644 --- a/ddtrace/appsec/_iast/taint_sinks/path_traversal.py +++ b/ddtrace/appsec/_iast/taint_sinks/path_traversal.py @@ -1,14 +1,10 @@ from typing import Any -from typing import Text from ddtrace.internal.logger import get_logger from ..._constants import IAST_SPAN_TAGS from .. import oce -from .._metrics import _set_metric_iast_instrumented_sink from .._metrics import increment_iast_span_metric -from .._patch import set_and_check_module_is_patched -from .._patch import set_module_unpatched from ..constants import VULN_PATH_TRAVERSAL from ..processor import AppSecIastSpanProcessor from ._base import VulnerabilityBase @@ -22,21 +18,6 @@ class PathTraversal(VulnerabilityBase): vulnerability_type = VULN_PATH_TRAVERSAL -def get_version() -> Text: - return "" - - -def unpatch_iast(): - set_module_unpatched("builtins", default_attr="_datadog_path_traversal_patch") - - -def patch(): - """Wrap functions which interact with file system.""" - if not set_and_check_module_is_patched("builtins", default_attr="_datadog_path_traversal_patch"): - return - _set_metric_iast_instrumented_sink(VULN_PATH_TRAVERSAL) - - def check_and_report_path_traversal(*args: Any, **kwargs: Any) -> None: if AppSecIastSpanProcessor.is_span_analyzed() and PathTraversal.has_quota(): try: @@ -45,14 +26,10 @@ def check_and_report_path_traversal(*args: Any, **kwargs: Any) -> None: increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, PathTraversal.vulnerability_type) _set_metric_iast_executed_sink(PathTraversal.vulnerability_type) - if is_pyobject_tainted(args[0]): - PathTraversal.report(evidence_value=args[0]) + filename_arg = args[0] if args else kwargs.get("file", None) + if is_pyobject_tainted(filename_arg): + PathTraversal.report(evidence_value=filename_arg) except Exception: log.debug("Unexpected exception while reporting vulnerability", exc_info=True) else: log.debug("IAST: no vulnerability quota to analyze more sink points") - - -def open_path_traversal(*args, **kwargs): - check_and_report_path_traversal(*args, **kwargs) - return open(*args, **kwargs) diff --git a/tests/appsec/iast/conftest.py b/tests/appsec/iast/conftest.py index fb2da25326b..03512871a2d 100644 --- a/tests/appsec/iast/conftest.py +++ b/tests/appsec/iast/conftest.py @@ -3,6 +3,8 @@ import pytest +from ddtrace.appsec._common_module_patches import patch_common_modules +from ddtrace.appsec._common_module_patches import unpatch_common_modules from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast import oce from ddtrace.appsec._iast._patches.json_tainting import patch as json_patch @@ -13,7 +15,6 @@ from ddtrace.appsec._iast.taint_sinks.command_injection import unpatch as cmdi_unpatch from ddtrace.appsec._iast.taint_sinks.header_injection import patch as header_injection_patch from ddtrace.appsec._iast.taint_sinks.header_injection import unpatch as header_injection_unpatch -from ddtrace.appsec._iast.taint_sinks.path_traversal import patch as path_traversal_patch from ddtrace.appsec._iast.taint_sinks.weak_cipher import patch as weak_cipher_patch from ddtrace.appsec._iast.taint_sinks.weak_cipher import unpatch_iast as weak_cipher_unpatch from ddtrace.appsec._iast.taint_sinks.weak_hash import patch as weak_hash_patch @@ -70,7 +71,6 @@ def iast_span(tracer, env, request_sampling="100", deduplication=False): span.span_type = "web" weak_hash_patch() weak_cipher_patch() - path_traversal_patch() sqli_sqlite_patch() json_patch() psycopg_patch() @@ -79,7 +79,9 @@ def iast_span(tracer, env, request_sampling="100", deduplication=False): header_injection_patch() langchain_patch() iast_span_processor.on_span_start(span) + patch_common_modules() yield span + unpatch_common_modules() iast_span_processor.on_span_finish(span) weak_hash_unpatch() weak_cipher_unpatch() diff --git a/tests/appsec/iast/taint_sinks/test_path_traversal.py b/tests/appsec/iast/taint_sinks/test_path_traversal.py index 5f5abaaa9c9..f7086a0cd59 100644 --- a/tests/appsec/iast/taint_sinks/test_path_traversal.py +++ b/tests/appsec/iast/taint_sinks/test_path_traversal.py @@ -1,5 +1,6 @@ import os +import mock import pytest from ddtrace.appsec._constants import IAST @@ -23,8 +24,6 @@ def _get_path_traversal_module_functions(): yield module, function -# FIXME: enable once the mock + open issue is fixed -@pytest.mark.skip def test_path_traversal_open(iast_span_defaults): mod = _iast_patched_module("tests.appsec.iast.fixtures.taint_sinks.path_traversal") @@ -51,8 +50,41 @@ def test_path_traversal_open(iast_span_defaults): assert vulnerability["evidence"].get("redacted") is None -# FIXME: enable once the mock + open issue is fixed -@pytest.mark.skip +@mock.patch("tests.appsec.iast.fixtures.taint_sinks.path_traversal.open") +def test_path_traversal_open_and_mock(mock_open, iast_span_defaults): + """Confirm we can mock the open function and IAST path traversal vulnerability is not reported""" + mod = _iast_patched_module("tests.appsec.iast.fixtures.taint_sinks.path_traversal") + + file_path = os.path.join(ROOT_DIR, "../fixtures", "taint_sinks", "path_traversal_test_file.txt") + + tainted_string = taint_pyobject( + file_path, source_name="path", source_value=file_path, source_origin=OriginType.PATH + ) + mod.pt_open(tainted_string) + + mock_open.assert_called_once_with(file_path) + + span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + assert span_report is None + + +def test_path_traversal_open_and_mock_after_patch_module(iast_span_defaults): + """Confirm we can mock the open function and IAST path traversal vulnerability is not reported""" + mod = _iast_patched_module("tests.appsec.iast.fixtures.taint_sinks.path_traversal") + with mock.patch("tests.appsec.iast.fixtures.taint_sinks.path_traversal.open") as mock_open: + file_path = os.path.join(ROOT_DIR, "../fixtures", "taint_sinks", "path_traversal_test_file.txt") + + tainted_string = taint_pyobject( + file_path, source_name="path", source_value=file_path, source_origin=OriginType.PATH + ) + mod.pt_open(tainted_string) + + mock_open.assert_called_once_with(file_path) + + span_report = core.get_item(IAST.CONTEXT_KEY, span=iast_span_defaults) + assert span_report is None + + @pytest.mark.parametrize( "file_path", ( @@ -74,8 +106,6 @@ def test_path_traversal_open_secure(file_path, iast_span_defaults): assert span_report is None -# FIXME: enable once the mock + open issue is fixed -@pytest.mark.skip @pytest.mark.parametrize( "module, function", _get_path_traversal_module_functions(), @@ -109,8 +139,6 @@ def test_path_traversal(module, function, iast_span_defaults): assert vulnerability["evidence"].get("redacted") is None -# FIXME: enable once the mock + open issue is fixed -@pytest.mark.skip @pytest.mark.parametrize("num_vuln_expected", [1, 0, 0]) def test_path_traversal_deduplication(num_vuln_expected, iast_span_deduplication_enabled): mod = _iast_patched_module("tests.appsec.iast.fixtures.taint_sinks.path_traversal") diff --git a/tests/appsec/iast/test_iast_propagation_path.py b/tests/appsec/iast/test_iast_propagation_path.py index f683b262eb1..9637b692501 100644 --- a/tests/appsec/iast/test_iast_propagation_path.py +++ b/tests/appsec/iast/test_iast_propagation_path.py @@ -27,8 +27,6 @@ def _assert_vulnerability(data, value_parts, file_line_label): assert vulnerability["hash"] == hash_value -# FIXME: enable once the mock + open issue is fixed -@pytest.mark.skip def test_propagation_no_path(iast_span_defaults): mod = _iast_patched_module("tests.appsec.iast.fixtures.propagation_path") origin1 = "taintsource" @@ -41,8 +39,6 @@ def test_propagation_no_path(iast_span_defaults): assert span_report is None -# FIXME: enable once the mock + open issue is fixed -@pytest.mark.skip @pytest.mark.parametrize( "origin1", [ @@ -77,8 +73,6 @@ def test_propagation_path_1_origin_1_propagation(origin1, iast_span_defaults): _assert_vulnerability(data, value_parts, "propagation_path_1_source_1_prop") -# FIXME: enable once the mock + open issue is fixed -@pytest.mark.skip @pytest.mark.parametrize( "origin1", [ @@ -115,8 +109,6 @@ def test_propagation_path_1_origins_2_propagations(origin1, iast_span_defaults): _assert_vulnerability(data, value_parts, "propagation_path_1_source_2_prop") -# FIXME: enable once the mock + open issue is fixed -@pytest.mark.skip @pytest.mark.parametrize( "origin1, origin2", [ @@ -167,8 +159,6 @@ def test_propagation_path_2_origins_2_propagations(origin1, origin2, iast_span_d _assert_vulnerability(data, value_parts, "propagation_path_2_source_2_prop") -# FIXME: enable once the mock + open issue is fixed -@pytest.mark.skip @pytest.mark.parametrize( "origin1, origin2", [ @@ -227,8 +217,6 @@ def test_propagation_path_2_origins_3_propagation(origin1, origin2, iast_span_de _assert_vulnerability(data, value_parts, "propagation_path_3_prop") -# FIXME: enable once the mock + open issue is fixed -@pytest.mark.skip @pytest.mark.parametrize( "origin1, origin2", [ diff --git a/tests/appsec/iast/test_telemetry.py b/tests/appsec/iast/test_telemetry.py index 2dfe1f11026..ac49a2700fb 100644 --- a/tests/appsec/iast/test_telemetry.py +++ b/tests/appsec/iast/test_telemetry.py @@ -1,6 +1,8 @@ import pytest from ddtrace.appsec import _asm_request_context +from ddtrace.appsec._common_module_patches import patch_common_modules +from ddtrace.appsec._common_module_patches import unpatch_common_modules from ddtrace.appsec._constants import IAST_SPAN_TAGS from ddtrace.appsec._handlers import _on_django_patch from ddtrace.appsec._iast._metrics import TELEMETRY_DEBUG_VERBOSITY @@ -19,8 +21,6 @@ from ddtrace.appsec._iast.taint_sinks.command_injection import patch as cmdi_patch from ddtrace.appsec._iast.taint_sinks.header_injection import patch as header_injection_patch from ddtrace.appsec._iast.taint_sinks.header_injection import unpatch as header_injection_unpatch -from ddtrace.appsec._iast.taint_sinks.path_traversal import patch as path_traversal_patch -from ddtrace.appsec._iast.taint_sinks.path_traversal import unpatch_iast as path_traversal_unpatch from ddtrace.contrib.sqlalchemy import patch as sqli_sqlalchemy_patch from ddtrace.contrib.sqlite3 import patch as sqli_sqlite3_patch from ddtrace.ext import SpanTypes @@ -107,11 +107,11 @@ def test_metric_instrumented_cmdi(no_request_sampling, telemetry_writer): def test_metric_instrumented_path_traversal(no_request_sampling, telemetry_writer): # We need to unpatch first because ddtrace.appsec._iast._patch_modules loads at runtime this patch function - path_traversal_unpatch() + unpatch_common_modules() with override_env(dict(DD_IAST_TELEMETRY_VERBOSITY="INFORMATION")), override_global_config( dict(_iast_enabled=True) ): - path_traversal_patch() + patch_common_modules() _assert_instrumented_sink(telemetry_writer, VULN_PATH_TRAVERSAL) From bf858f73768263ea7ed9743fff87d67d213afc11 Mon Sep 17 00:00:00 2001 From: lievan <42917263+lievan@users.noreply.github.com> Date: Fri, 17 May 2024 13:40:07 -0400 Subject: [PATCH 068/104] feat(llmobs): support in-code config for llmobs (#9172) Support in-code configuration for LLMObs users, to enable LLMObs and specify the following configurations that currently require environment variable configuration. - ml_app - list of integrations to patch (will patch all LLMObs integrations by default) - dd_llmobs_no_apm (turn off APM, telemetry, remote config, metrics) - DD site, DD env, DD service (will override config/env vars) ``` from ddtrace.llmobs import LLMObs LLMObs.enable( ml_app="comms/langchain", integrations=["openai"], llmobs_agentless_enabled=True, # api_key =... # site=... # env=... # service=... # _tracer=None ) ``` Allowing in-code setup also improves the dev experience for people tracing experimental apps with LLMObs. It also abstracts away a long list of environment variables non-APM customers are required to set to turn off all APM related features. This PR should not break _any_ previous way of setting up the Python SDK (e.g. using env vars and `ddtrace-run`). Arguments passed to enable() should take precedence over environment variables, with the exception of `DD_LLMOBS_ENABLED`. This PR also does a couple minor things: - If `DD_LLMOBS_NO_APM` env var is detected or configured through LLMObs.enable(), the OpenAI and LangChain integrations will disable submitting metrics unless the corresponding env vars `DD_{OPENAI,LANGCHAIN}_METRICS_ENABLED` is set to True. - We also automatically disable both telemetry writer and remote config pollers if `DD_LLMOBS_NO_APM` is detected or configured through LLMObs.enable(). - We automatically patch the LLMObs integrations on LLMObs.enable(). - Removes all LLMObs.enable() references in individual integration patch code (openai, botocore, langchain) Note: - This change (only for LLMObs users) will override `config.service, config.env` if these are passed in to `LLMObs.enable()`. - If a user runs via `ddtrace-run`, they cannot use `LLMObs.enable()` to configure their settings. ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: lievan Co-authored-by: Yun Kim <35776586+Yun-Kim@users.noreply.github.com> Co-authored-by: Yun Kim --- ddtrace/contrib/botocore/patch.py | 6 -- ddtrace/contrib/langchain/patch.py | 7 -- ddtrace/contrib/openai/patch.py | 7 +- ddtrace/llmobs/_integrations/base.py | 6 +- ddtrace/llmobs/_llmobs.py | 93 ++++++++++++++++++- ddtrace/llmobs/_trace_processor.py | 2 +- tests/contrib/botocore/test_bedrock.py | 7 +- tests/contrib/langchain/conftest.py | 2 + tests/contrib/langchain/test_langchain.py | 4 +- .../langchain/test_langchain_community.py | 4 +- tests/contrib/openai/conftest.py | 3 +- tests/llmobs/conftest.py | 2 +- tests/llmobs/test_llmobs_service.py | 40 ++++++-- tests/llmobs/test_llmobs_trace_processor.py | 9 +- 14 files changed, 146 insertions(+), 46 deletions(-) diff --git a/ddtrace/contrib/botocore/patch.py b/ddtrace/contrib/botocore/patch.py index b4f1a5265ea..b0db100b81e 100644 --- a/ddtrace/contrib/botocore/patch.py +++ b/ddtrace/contrib/botocore/patch.py @@ -13,7 +13,6 @@ from ddtrace import config from ddtrace.contrib.trace_utils import with_traced_module -from ddtrace.llmobs import LLMObs from ddtrace.llmobs._integrations import BedrockIntegration from ddtrace.settings.config import Config from ddtrace.vendor import wrapt @@ -86,9 +85,6 @@ def patch(): return botocore.client._datadog_patch = True - if config._llmobs_enabled: - LLMObs.enable() - botocore._datadog_integration = BedrockIntegration(integration_config=config.botocore) wrapt.wrap_function_wrapper("botocore.client", "BaseClient._make_api_call", patched_api_call(botocore)) Pin(service="aws").onto(botocore.client.BaseClient) @@ -103,8 +99,6 @@ def unpatch(): botocore.client._datadog_patch = False unwrap(botocore.parsers.ResponseParser, "parse") unwrap(botocore.client.BaseClient, "_make_api_call") - if LLMObs.enabled: - LLMObs.disable() def patch_submodules(submodules): diff --git a/ddtrace/contrib/langchain/patch.py b/ddtrace/contrib/langchain/patch.py index 019b0c489b3..d24d8b53348 100644 --- a/ddtrace/contrib/langchain/patch.py +++ b/ddtrace/contrib/langchain/patch.py @@ -47,7 +47,6 @@ from ddtrace.internal.utils.formats import asbool from ddtrace.internal.utils.formats import deep_getattr from ddtrace.internal.utils.version import parse_version -from ddtrace.llmobs import LLMObs from ddtrace.llmobs._integrations import LangChainIntegration from ddtrace.pin import Pin from ddtrace.vendor import wrapt @@ -885,9 +884,6 @@ def patch(): if getattr(langchain, "_datadog_patch", False): return - if config._llmobs_enabled: - LLMObs.enable() - langchain._datadog_patch = True Pin().onto(langchain) @@ -995,9 +991,6 @@ def unpatch(): if not getattr(langchain, "_datadog_patch", False): return - if LLMObs.enabled: - LLMObs.disable() - langchain._datadog_patch = False if SHOULD_PATCH_LANGCHAIN_COMMUNITY: diff --git a/ddtrace/contrib/openai/patch.py b/ddtrace/contrib/openai/patch.py index 5a504e1258c..aca4693ede2 100644 --- a/ddtrace/contrib/openai/patch.py +++ b/ddtrace/contrib/openai/patch.py @@ -10,7 +10,6 @@ from ddtrace.internal.utils.formats import deep_getattr from ddtrace.internal.utils.version import parse_version from ddtrace.internal.wrapping import wrap -from ddtrace.llmobs import LLMObs from ddtrace.llmobs._integrations import OpenAIIntegration from ...pin import Pin @@ -148,9 +147,6 @@ def patch(): if getattr(openai, "__datadog_patch", False): return - if config._llmobs_enabled: - LLMObs.enable() - Pin().onto(openai) integration = OpenAIIntegration(integration_config=config.openai, openai=openai) @@ -202,8 +198,7 @@ def patch(): def unpatch(): # FIXME: add unpatching. The current wrapping.unwrap method requires # the wrapper function to be provided which we don't keep a reference to. - if LLMObs.enabled: - LLMObs.disable() + pass def _patched_client_init(openai, integration): diff --git a/ddtrace/llmobs/_integrations/base.py b/ddtrace/llmobs/_integrations/base.py index 8e5396baa62..8a7c3f99086 100644 --- a/ddtrace/llmobs/_integrations/base.py +++ b/ddtrace/llmobs/_integrations/base.py @@ -52,12 +52,14 @@ def __init__(self, integration_config: IntegrationConfig) -> None: ) self._log_pc_sampler = RateSampler(sample_rate=integration_config.log_prompt_completion_sample_rate) self.start_log_writer() - if self.llmobs_enabled: - self._llmobs_pc_sampler = RateSampler(sample_rate=config._llmobs_sample_rate) + self._llmobs_pc_sampler = RateSampler(sample_rate=config._llmobs_sample_rate) @property def metrics_enabled(self) -> bool: """Return whether submitting metrics is enabled for this integration, or global config if not set.""" + env_metrics_enabled = asbool(os.getenv("DD_{}_METRICS_ENABLED".format(self._integration_name.upper()))) + if not env_metrics_enabled and asbool(os.getenv("DD_LLMOBS_AGENTLESS_ENABLED")): + return False if hasattr(self.integration_config, "metrics_enabled"): return asbool(self.integration_config.metrics_enabled) return False diff --git a/ddtrace/llmobs/_llmobs.py b/ddtrace/llmobs/_llmobs.py index 8cd59bdc4af..fef9bdaf5f0 100644 --- a/ddtrace/llmobs/_llmobs.py +++ b/ddtrace/llmobs/_llmobs.py @@ -2,15 +2,19 @@ import os from typing import Any from typing import Dict +from typing import List from typing import Optional from typing import Union import ddtrace from ddtrace import Span from ddtrace import config +from ddtrace import patch from ddtrace.ext import SpanTypes from ddtrace.internal import atexit +from ddtrace.internal import telemetry from ddtrace.internal.logger import get_logger +from ddtrace.internal.remoteconfig.worker import remoteconfig_poller from ddtrace.internal.service import Service from ddtrace.internal.service import ServiceStatusError from ddtrace.internal.utils.formats import asbool @@ -43,6 +47,13 @@ log = get_logger(__name__) +SUPPORTED_INTEGRATIONS = { + "bedrock": lambda: patch(botocore=True), + "langchain": lambda: patch(langchain=True), + "openai": lambda: patch(openai=True), +} + + class LLMObs(Service): _instance = None # type: LLMObs enabled = False @@ -88,7 +99,29 @@ def _stop_service(self) -> None: log.warning("Failed to shutdown tracer", exc_info=True) @classmethod - def enable(cls, tracer=None): + def enable( + cls, + ml_app: Optional[str] = None, + integrations: Optional[List[str]] = None, + agentless_enabled: bool = False, + site: Optional[str] = None, + api_key: Optional[str] = None, + env: Optional[str] = None, + service: Optional[str] = None, + _tracer=None, + ): + """ + Enable LLM Observability tracing. + + :param str ml_app: The name of your ml application. + :param List[str] integrations: A list of integrations to enable auto-tracing for. + Must be subset of ("openai", "langchain", "bedrock") + :param bool agentless_enabled: Set to `true` to disable sending data that requires a Datadog Agent. + :param str site: Your datadog site. + :param str api_key: Your datadog api key. + :param str env: Your environment name. + :param str service: Your service name. + """ if cls.enabled: log.debug("%s already enabled", cls.__name__) return @@ -97,23 +130,47 @@ def enable(cls, tracer=None): log.debug("LLMObs.enable() called when DD_LLMOBS_ENABLED is set to false or 0, not starting LLMObs service") return + # grab required values for LLMObs + config._dd_site = site or config._dd_site + config._dd_api_key = api_key or config._dd_api_key + config._llmobs_ml_app = ml_app or config._llmobs_ml_app + config.env = env or config.env + config.service = service or config.service + + # validate required values for LLMObs if not config._dd_api_key: raise ValueError( "DD_API_KEY is required for sending LLMObs data. " "Ensure this configuration is set before running your application." ) + if not config._dd_site: + raise ValueError( + "DD_SITE is required for sending LLMObs data. " + "Ensure this configuration is set before running your application." + ) if not config._llmobs_ml_app: raise ValueError( "DD_LLMOBS_APP_NAME is required for sending LLMObs data. " "Ensure this configuration is set before running your application." ) - # override the default _instance with a new tracer - cls._instance = cls(tracer=tracer) + if agentless_enabled or asbool(os.getenv("DD_LLMOBS_AGENTLESS_ENABLED", "false")): + os.environ["DD_LLMOBS_AGENTLESS_ENABLED"] = "1" - cls.enabled = True + if not os.getenv("DD_INSTRUMENTATION_TELEMETRY_ENABLED"): + config._telemetry_enabled = False + log.debug("Telemetry disabled because DD_LLMOBS_AGENTLESS_ENABLED is set to true.") + telemetry.telemetry_writer.disable() + + if not os.getenv("DD_REMOTE_CONFIG_ENABLED"): + config._remote_config_enabled = False + log.debug("Remote configuration disabled because DD_LLMOBS_AGENTLESS_ENABLED is set to true.") + remoteconfig_poller.disable() - # turn on llmobs trace processing + cls._patch_integrations(integrations) + # override the default _instance with a new tracer + cls._instance = cls(tracer=_tracer) + cls.enabled = True cls._instance.start() atexit.register(cls.disable) @@ -146,6 +203,32 @@ def flush(cls): except Exception: log.warning("Failed to flush LLMObs spans and evaluation metrics.", exc_info=True) + @staticmethod + def _patch_integrations(integrations: Optional[List[str]] = None): + """ + Patch LLM integrations based on a list of integrations passed in. Patch all supported integrations by default. + """ + integrations_to_patch = {} + if integrations is None: + integrations_to_patch.update(SUPPORTED_INTEGRATIONS) + else: + for integration in integrations: + integration = integration.lower() + if integration in SUPPORTED_INTEGRATIONS: + integrations_to_patch.update({integration: SUPPORTED_INTEGRATIONS[integration]}) + else: + log.warning( + "%s is unsupported - LLMObs currently supports %s", + integration, + str(SUPPORTED_INTEGRATIONS.keys()), + ) + for integration in integrations_to_patch: + try: + SUPPORTED_INTEGRATIONS[integration]() + except Exception: + log.warning("couldn't patch %s", integration, exc_info=True) + return + @classmethod def export_span(cls, span: Optional[Span] = None) -> Optional[ExportedLLMObsSpan]: """Returns a simple representation of a span to export its span and trace IDs. diff --git a/ddtrace/llmobs/_trace_processor.py b/ddtrace/llmobs/_trace_processor.py index ac07cf1d484..815ada53461 100644 --- a/ddtrace/llmobs/_trace_processor.py +++ b/ddtrace/llmobs/_trace_processor.py @@ -45,7 +45,7 @@ class LLMObsTraceProcessor(TraceProcessor): def __init__(self, llmobs_span_writer): self._span_writer = llmobs_span_writer - self._no_apm_traces = asbool(os.getenv("DD_LLMOBS_NO_APM", False)) + self._no_apm_traces = asbool(os.getenv("DD_LLMOBS_AGENTLESS_ENABLED", False)) def process_trace(self, trace: List[Span]) -> Optional[List[Span]]: if not trace: diff --git a/tests/contrib/botocore/test_bedrock.py b/tests/contrib/botocore/test_bedrock.py index a94699813ed..d2f727f0372 100644 --- a/tests/contrib/botocore/test_bedrock.py +++ b/tests/contrib/botocore/test_bedrock.py @@ -153,6 +153,7 @@ def bedrock_client(boto3, request_vcr): ) bedrock_client = session.client("bedrock-runtime") yield bedrock_client + LLMObs.disable() @pytest.fixture @@ -487,7 +488,7 @@ def _test_llmobs_invoke(cls, provider, bedrock_client, mock_llmobs_span_writer, pin.override(bedrock_client, tracer=mock_tracer) # Need to disable and re-enable LLMObs service to use the mock tracer LLMObs.disable() - LLMObs.enable(tracer=mock_tracer) + LLMObs.enable(_tracer=mock_tracer, integrations=["bedrock"]) if cassette_name is None: cassette_name = "%s_invoke.yaml" % provider @@ -523,7 +524,7 @@ def _test_llmobs_invoke_stream( pin.override(bedrock_client, tracer=mock_tracer) # Need to disable and re-enable LLMObs service to use the mock tracer LLMObs.disable() - LLMObs.enable(tracer=mock_tracer) + LLMObs.enable(_tracer=mock_tracer, integrations=["bedrock"]) if cassette_name is None: cassette_name = "%s_invoke_stream.yaml" % provider @@ -623,7 +624,7 @@ def test_llmobs_error(self, ddtrace_global_config, bedrock_client, mock_llmobs_s pin.override(bedrock_client, tracer=mock_tracer) # Need to disable and re-enable LLMObs service to use the mock tracer LLMObs.disable() - LLMObs.enable(tracer=mock_tracer) + LLMObs.enable(_tracer=mock_tracer, integrations=["bedrock"]) with pytest.raises(botocore.exceptions.ClientError): with request_vcr.use_cassette("meta_invoke_error.yaml"): body, model = json.dumps(_REQUEST_BODIES["meta"]), _MODELS["meta"] diff --git a/tests/contrib/langchain/conftest.py b/tests/contrib/langchain/conftest.py index d0df10ec929..c6595e295c0 100644 --- a/tests/contrib/langchain/conftest.py +++ b/tests/contrib/langchain/conftest.py @@ -6,6 +6,7 @@ from ddtrace import Pin from ddtrace.contrib.langchain.patch import patch from ddtrace.contrib.langchain.patch import unpatch +from ddtrace.llmobs import LLMObs from tests.utils import DummyTracer from tests.utils import DummyWriter from tests.utils import override_config @@ -86,6 +87,7 @@ def mock_llmobs_span_writer(): yield m finally: patcher.stop() + LLMObs.disable() @pytest.fixture diff --git a/tests/contrib/langchain/test_langchain.py b/tests/contrib/langchain/test_langchain.py index 3597c2efbee..43d1e6153fd 100644 --- a/tests/contrib/langchain/test_langchain.py +++ b/tests/contrib/langchain/test_langchain.py @@ -1350,7 +1350,7 @@ def _test_llmobs_llm_invoke( different_py39_cassette=False, ): LLMObs.disable() - LLMObs.enable(tracer=mock_tracer) + LLMObs.enable(_tracer=mock_tracer, integrations=["langchain"]) if sys.version_info < (3, 10, 0) and different_py39_cassette: cassette_name = cassette_name.replace(".yaml", "_39.yaml") @@ -1386,7 +1386,7 @@ def _test_llmobs_chain_invoke( ): # disable the service before re-enabling it, as it was enabled in another test LLMObs.disable() - LLMObs.enable(tracer=mock_tracer) + LLMObs.enable(_tracer=mock_tracer, integrations=["langchain"]) if sys.version_info < (3, 10, 0) and different_py39_cassette: cassette_name = cassette_name.replace(".yaml", "_39.yaml") diff --git a/tests/contrib/langchain/test_langchain_community.py b/tests/contrib/langchain/test_langchain_community.py index e1a59a68703..929e53d6e68 100644 --- a/tests/contrib/langchain/test_langchain_community.py +++ b/tests/contrib/langchain/test_langchain_community.py @@ -1337,7 +1337,7 @@ def _test_llmobs_llm_invoke( output_role=None, ): LLMObs.disable() - LLMObs.enable(tracer=mock_tracer) + LLMObs.enable(_tracer=mock_tracer, integrations=["langchain"]) with request_vcr.use_cassette(cassette_name): generate_trace("Can you explain what an LLM chain is?") @@ -1370,7 +1370,7 @@ def _test_llmobs_chain_invoke( ): # disable the service before re-enabling it, as it was enabled in another test LLMObs.disable() - LLMObs.enable(tracer=mock_tracer) + LLMObs.enable(_tracer=mock_tracer, integrations=["langchain"]) with request_vcr.use_cassette(cassette_name): generate_trace("Can you explain what an LLM chain is?") diff --git a/tests/contrib/openai/conftest.py b/tests/contrib/openai/conftest.py index 542c1568236..f07e64787fa 100644 --- a/tests/contrib/openai/conftest.py +++ b/tests/contrib/openai/conftest.py @@ -191,9 +191,10 @@ def mock_tracer(ddtrace_global_config, openai, patch_openai, mock_logs, mock_met if ddtrace_global_config.get("_llmobs_enabled", False): # Have to disable and re-enable LLMObs to use to mock tracer. LLMObs.disable() - LLMObs.enable(tracer=mock_tracer) + LLMObs.enable(_tracer=mock_tracer, integrations=["openai"]) yield mock_tracer mock_logs.reset_mock() mock_metrics.reset_mock() + LLMObs.disable() diff --git a/tests/llmobs/conftest.py b/tests/llmobs/conftest.py index a0bc2daaec2..4287be56ec9 100644 --- a/tests/llmobs/conftest.py +++ b/tests/llmobs/conftest.py @@ -69,6 +69,6 @@ def LLMObs(mock_llmobs_span_writer, mock_llmobs_eval_metric_writer, ddtrace_glob global_config.update(ddtrace_global_config) with override_global_config(global_config): dummy_tracer = DummyTracer() - llmobs_service.enable(tracer=dummy_tracer) + llmobs_service.enable(_tracer=dummy_tracer) yield llmobs_service llmobs_service.disable() diff --git a/tests/llmobs/test_llmobs_service.py b/tests/llmobs/test_llmobs_service.py index 74717cdf47a..75c8174edcd 100644 --- a/tests/llmobs/test_llmobs_service.py +++ b/tests/llmobs/test_llmobs_service.py @@ -3,6 +3,8 @@ import mock import pytest +from ddtrace._trace.span import Span +from ddtrace.ext import SpanTypes from ddtrace.llmobs import LLMObs as llmobs_service from ddtrace.llmobs._constants import INPUT_DOCUMENTS from ddtrace.llmobs._constants import INPUT_MESSAGES @@ -37,22 +39,48 @@ def mock_logs(): yield mock_logs +def run_llmobs_trace_filter(dummy_tracer): + for trace_filter in dummy_tracer._filters: + if isinstance(trace_filter, LLMObsTraceProcessor): + root_llm_span = Span(name="span1", span_type=SpanTypes.LLM) + root_llm_span.set_tag_str(SPAN_KIND, "llm") + trace1 = [root_llm_span] + return trace_filter.process_trace(trace1) + raise ValueError("LLMObsTraceProcessor not found in tracer filters.") + + def test_service_enable(): with override_global_config(dict(_dd_api_key="", _llmobs_ml_app="")): dummy_tracer = DummyTracer() - llmobs_service.enable(tracer=dummy_tracer) + llmobs_service.enable(_tracer=dummy_tracer) + llmobs_instance = llmobs_service._instance + assert llmobs_instance is not None + assert llmobs_service.enabled + assert llmobs_instance.tracer == dummy_tracer + assert any(isinstance(tracer_filter, LLMObsTraceProcessor) for tracer_filter in dummy_tracer._filters) + assert run_llmobs_trace_filter(dummy_tracer) is not None + + llmobs_service.disable() + + +def test_service_enable_with_apm_disabled(monkeypatch): + with override_global_config(dict(_dd_api_key="", _llmobs_ml_app="")): + dummy_tracer = DummyTracer() + llmobs_service.enable(_tracer=dummy_tracer, agentless_enabled=True) llmobs_instance = llmobs_service._instance assert llmobs_instance is not None assert llmobs_service.enabled assert llmobs_instance.tracer == dummy_tracer assert any(isinstance(tracer_filter, LLMObsTraceProcessor) for tracer_filter in dummy_tracer._filters) + assert run_llmobs_trace_filter(dummy_tracer) is None + llmobs_service.disable() def test_service_disable(): with override_global_config(dict(_dd_api_key="", _llmobs_ml_app="")): dummy_tracer = DummyTracer() - llmobs_service.enable(tracer=dummy_tracer) + llmobs_service.enable(_tracer=dummy_tracer) llmobs_service.disable() assert llmobs_service.enabled is False assert llmobs_service._instance._llmobs_eval_metric_writer.status.value == "stopped" @@ -63,7 +91,7 @@ def test_service_enable_no_api_key(): with override_global_config(dict(_dd_api_key="", _llmobs_ml_app="")): dummy_tracer = DummyTracer() with pytest.raises(ValueError): - llmobs_service.enable(tracer=dummy_tracer) + llmobs_service.enable(_tracer=dummy_tracer) assert llmobs_service.enabled is False assert llmobs_service._instance._llmobs_eval_metric_writer.status.value == "stopped" assert llmobs_service._instance._llmobs_span_writer.status.value == "stopped" @@ -73,7 +101,7 @@ def test_service_enable_no_ml_app_specified(): with override_global_config(dict(_dd_api_key="", _llmobs_ml_app="")): dummy_tracer = DummyTracer() with pytest.raises(ValueError): - llmobs_service.enable(tracer=dummy_tracer) + llmobs_service.enable(_tracer=dummy_tracer) assert llmobs_service.enabled is False assert llmobs_service._instance._llmobs_eval_metric_writer.status.value == "stopped" assert llmobs_service._instance._llmobs_span_writer.status.value == "stopped" @@ -82,8 +110,8 @@ def test_service_enable_no_ml_app_specified(): def test_service_enable_already_enabled(mock_logs): with override_global_config(dict(_dd_api_key="", _llmobs_ml_app="")): dummy_tracer = DummyTracer() - llmobs_service.enable(tracer=dummy_tracer) - llmobs_service.enable(tracer=dummy_tracer) + llmobs_service.enable(_tracer=dummy_tracer) + llmobs_service.enable(_tracer=dummy_tracer) llmobs_instance = llmobs_service._instance assert llmobs_instance is not None assert llmobs_service.enabled diff --git a/tests/llmobs/test_llmobs_trace_processor.py b/tests/llmobs/test_llmobs_trace_processor.py index 85d9f0541c2..828dc547219 100644 --- a/tests/llmobs/test_llmobs_trace_processor.py +++ b/tests/llmobs/test_llmobs_trace_processor.py @@ -30,6 +30,7 @@ def mock_logs(): def test_processor_returns_all_traces_by_default(monkeypatch): + monkeypatch.delenv("DD_LLMOBS_AGENTLESS_ENABLED", raising=False) """Test that the LLMObsTraceProcessor returns all traces by default.""" trace_filter = LLMObsTraceProcessor(llmobs_span_writer=mock.MagicMock()) root_llm_span = Span(name="span1", span_type=SpanTypes.LLM) @@ -39,8 +40,8 @@ def test_processor_returns_all_traces_by_default(monkeypatch): def test_processor_returns_all_traces_if_no_apm_env_var_is_false(monkeypatch): - """Test that the LLMObsTraceProcessor returns all traces if DD_LLMOBS_NO_APM is not set to true.""" - monkeypatch.setenv("DD_LLMOBS_NO_APM", "0") + """Test that the LLMObsTraceProcessor returns all traces if DD_LLMOBS_AGENTLESS_ENABLED is not set to true.""" + monkeypatch.setenv("DD_LLMOBS_AGENTLESS_ENABLED", "0") trace_filter = LLMObsTraceProcessor(llmobs_span_writer=mock.MagicMock()) root_llm_span = Span(name="span1", span_type=SpanTypes.LLM) root_llm_span.set_tag_str(SPAN_KIND, "llm") @@ -49,8 +50,8 @@ def test_processor_returns_all_traces_if_no_apm_env_var_is_false(monkeypatch): def test_processor_returns_none_if_no_apm_env_var_is_true(monkeypatch): - """Test that the LLMObsTraceProcessor returns None if DD_LLMOBS_NO_APM is set to true.""" - monkeypatch.setenv("DD_LLMOBS_NO_APM", "1") + """Test that the LLMObsTraceProcessor returns None if DD_LLMOBS_AGENTLESS_ENABLED is set to true.""" + monkeypatch.setenv("DD_LLMOBS_AGENTLESS_ENABLED", "1") trace_filter = LLMObsTraceProcessor(llmobs_span_writer=mock.MagicMock()) root_llm_span = Span(name="span1", span_type=SpanTypes.LLM) root_llm_span.set_tag_str(SPAN_KIND, "llm") From b338e332528d0f3952da628d6618d331cc181f9d Mon Sep 17 00:00:00 2001 From: William Conti <58711692+wconti27@users.noreply.github.com> Date: Fri, 17 May 2024 14:18:44 -0400 Subject: [PATCH 069/104] chore: update changelog for version 2.8.5 (#9310) - [x] update changelog for version 2.8.5 --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8e7f92afcf..e4375585762 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ Changelogs for versions not listed here can be found at https://github.com/DataDog/dd-trace-py/releases +--- + +## 2.8.5 + + +### Known Issues + +- Code Security: Security tracing for the `builtins.open` function is experimental and may not be stable. This aspect is not replaced by default. +- grpc: Tracing for the `grpc.aio` clients and servers is experimental and may not be stable. This integration is now disabled by default. + +### Bug Fixes + +- fix(grpc): This fix a bug in the grpc.aio support specific to streaming responses. +- RemoteConfig: This fix resolves an issue where remote config did not work for the tracer when using an agent that would add a flare item to the remote config payload. With this fix, the tracer will now correctly pull out the lib_config we need from the payload in order to implement remote config changes properly. + + --- ## 2.8.4 From 9d6ad1706473af45565c42ce64eb0731fece64fd Mon Sep 17 00:00:00 2001 From: Yun Kim <35776586+Yun-Kim@users.noreply.github.com> Date: Fri, 17 May 2024 16:15:01 -0400 Subject: [PATCH 070/104] fix(openai): handle explicit n=None case for chat completions (#9297) Fixes #9283. If users explicitly pass in `n=None` as a kwarg for OpenAI's completion/chat endpoints, this will break our tracing as we rely on the value of the passed `n` to instantiate a list of `n` elements. Normally if `n` is not passed into the chat/completion method we assume a default value of 1 via `kwargs.get("n", 1)` but if `n` is explicitly passed as None this breaks our check. This fix ensures we default to a `n=1` in this case. ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/contrib/openai/_endpoint_hooks.py | 4 ++-- .../notes/fix-openai-none-n-kwarg-04e0f81074a13191.yaml | 4 ++++ tests/contrib/openai/test_openai_v1.py | 3 ++- 3 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/fix-openai-none-n-kwarg-04e0f81074a13191.yaml diff --git a/ddtrace/contrib/openai/_endpoint_hooks.py b/ddtrace/contrib/openai/_endpoint_hooks.py index ce3568e4006..53e11344994 100644 --- a/ddtrace/contrib/openai/_endpoint_hooks.py +++ b/ddtrace/contrib/openai/_endpoint_hooks.py @@ -135,7 +135,7 @@ def shared_gen(): async def traced_streamed_response(): g = shared_gen() g.send(None) - n = kwargs.get("n", 1) + n = kwargs.get("n", 1) or 1 if operation_id == _CompletionHook.OPERATION_ID: prompts = kwargs.get("prompt", "") if isinstance(prompts, list) and not isinstance(prompts[0], int): @@ -158,7 +158,7 @@ async def traced_streamed_response(): def traced_streamed_response(): g = shared_gen() g.send(None) - n = kwargs.get("n", 1) + n = kwargs.get("n", 1) or 1 if operation_id == _CompletionHook.OPERATION_ID: prompts = kwargs.get("prompt", "") if isinstance(prompts, list) and not isinstance(prompts[0], int): diff --git a/releasenotes/notes/fix-openai-none-n-kwarg-04e0f81074a13191.yaml b/releasenotes/notes/fix-openai-none-n-kwarg-04e0f81074a13191.yaml new file mode 100644 index 00000000000..d3455df4447 --- /dev/null +++ b/releasenotes/notes/fix-openai-none-n-kwarg-04e0f81074a13191.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + openai: This fix resolves an issue where specifying `n=None` for streamed chat completions resulted in a `TypeError`. diff --git a/tests/contrib/openai/test_openai_v1.py b/tests/contrib/openai/test_openai_v1.py index 85bd60147b1..e8928844d17 100644 --- a/tests/contrib/openai/test_openai_v1.py +++ b/tests/contrib/openai/test_openai_v1.py @@ -1192,7 +1192,7 @@ def test_completion_stream(openai, openai_vcr, mock_metrics, mock_tracer): mock_encoding.return_value.encode.side_effect = lambda x: [1, 2] expected_completion = '! ... A page layouts page drawer? ... Interesting. The "Tools" is' client = openai.OpenAI() - resp = client.completions.create(model="ada", prompt="Hello world", stream=True) + resp = client.completions.create(model="ada", prompt="Hello world", stream=True, n=None) assert isinstance(resp, Generator) chunks = [c for c in resp] @@ -1278,6 +1278,7 @@ def test_chat_completion_stream(openai, openai_vcr, mock_metrics, snapshot_trace ], stream=True, user="ddtrace-test", + n=None, ) assert isinstance(resp, Generator) prompt_tokens = 8 From a6735c531b148f266ddf9d3e4f2f7187fd052f0e Mon Sep 17 00:00:00 2001 From: Juanjo Alvarez Martinez Date: Mon, 20 May 2024 11:46:34 +0200 Subject: [PATCH 071/104] chore: native asm code linting (#9316) ## Description This includes many small linting-style changes including: - const correctness - include header cleanup (which actually improves compile time). - C++ conventions (no underscores, C++ casts). - et cetera... ## Checklist - [X] Change(s) are motivated and described in the PR description - [X] Testing strategy is described if automated tests are not included in the PR - [X] Risks are described (performance impact, potential for breakage, maintainability) - [X] Change is maintainable (easy to change, telemetry, documentation) - [X] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [X] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [X] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [X] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [ ] Title is accurate - [ ] All changes are related to the pull request's stated goal - [ ] Description motivates each change - [ ] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [ ] Testing strategy adequately addresses listed risks - [ ] Change is maintainable (easy to change, telemetry, documentation) - [ ] Release note makes sense to a user of the library - [ ] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [ ] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Signed-off-by: Juanjo Alvarez --- .../_taint_tracking/Aspects/AspectExtend.cpp | 2 +- .../_taint_tracking/Aspects/AspectFormat.cpp | 8 +- .../_taint_tracking/Aspects/AspectFormat.h | 3 - .../_taint_tracking/Aspects/AspectIndex.cpp | 13 +-- .../_taint_tracking/Aspects/AspectIndex.h | 1 - .../_taint_tracking/Aspects/AspectJoin.cpp | 7 +- .../_taint_tracking/Aspects/AspectJoin.h | 2 - .../Aspects/AspectOperatorAdd.cpp | 8 +- .../Aspects/AspectOperatorAdd.h | 2 - .../_taint_tracking/Aspects/AspectSlice.cpp | 40 ++++---- .../_taint_tracking/Aspects/AspectSlice.h | 1 - .../_taint_tracking/Aspects/AspectSplit.cpp | 21 ++-- .../_taint_tracking/Aspects/AspectSplit.h | 4 +- .../_taint_tracking/Aspects/AspectsOsPath.cpp | 61 +++++------- .../_taint_tracking/Aspects/AspectsOsPath.h | 2 - .../_iast/_taint_tracking/Aspects/Helpers.cpp | 95 +++++++++---------- .../_iast/_taint_tracking/Aspects/Helpers.h | 23 +++-- .../Initializer/Initializer.cpp | 31 +++--- .../_taint_tracking/Initializer/Initializer.h | 6 +- .../_taint_tracking/TaintTracking/Source.cpp | 12 +-- .../_taint_tracking/TaintTracking/Source.h | 13 +-- .../TaintTracking/TaintRange.cpp | 95 ++++++++----------- .../TaintTracking/TaintRange.h | 12 +-- .../TaintTracking/TaintedObject.cpp | 38 ++++---- .../TaintTracking/TaintedObject.h | 6 +- .../TaintTracking/_taint_tracking.h | 4 - .../_taint_tracking/TaintedOps/TaintedOps.cpp | 2 +- .../_taint_tracking/TaintedOps/TaintedOps.h | 4 - .../_taint_tracking/Utils/StringUtils.cpp | 21 ++-- .../_iast/_taint_tracking/Utils/StringUtils.h | 2 +- .../appsec/_iast/_taint_tracking/_native.cpp | 2 - 31 files changed, 234 insertions(+), 307 deletions(-) diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectExtend.cpp b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectExtend.cpp index e931a8f2997..e571713342d 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectExtend.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectExtend.cpp @@ -9,7 +9,7 @@ * @return PyObject*: return None (Remember, Pyobject None isn't the same as nullptr) */ PyObject* -api_extend_aspect(PyObject* self, PyObject* const* args, Py_ssize_t nargs) +api_extend_aspect(PyObject* self, PyObject* const* args, const Py_ssize_t nargs) { if (nargs != 2 or !args) { throw py::value_error(MSG_ERROR_N_PARAMS); diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectFormat.cpp b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectFormat.cpp index 33295dec186..a4e80da39e0 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectFormat.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectFormat.cpp @@ -27,14 +27,14 @@ api_format_aspect(StrType& candidate_text, if (!ranges_orig.empty() or !candidate_text_ranges.empty()) { auto new_template = - _int_as_formatted_evidence(candidate_text, candidate_text_ranges, TagMappingMode::Mapper); + int_as_formatted_evidence(candidate_text, candidate_text_ranges, TagMappingMode::Mapper); py::list new_args; py::dict new_kwargs; for (const auto arg : args) { if (is_text(arg.ptr())) { auto str_arg = py::cast(arg); - auto n_arg = _all_as_formatted_evidence(str_arg, TagMappingMode::Mapper); + auto n_arg = all_as_formatted_evidence(str_arg, TagMappingMode::Mapper); new_args.append(n_arg); } else { new_args.append(arg); @@ -43,7 +43,7 @@ api_format_aspect(StrType& candidate_text, for (auto [key, value] : kwargs) { if (is_text(value.ptr())) { auto str_value = py::cast(value); - auto n_value = _all_as_formatted_evidence(str_value, TagMappingMode::Mapper); + auto n_value = all_as_formatted_evidence(str_value, TagMappingMode::Mapper); new_kwargs[key] = n_value; } else { new_kwargs[key] = value; @@ -51,7 +51,7 @@ api_format_aspect(StrType& candidate_text, } StrType new_template_format = py::getattr(new_template, "format")(*(py::cast(new_args)), **new_kwargs); - std::tuple result = _convert_escaped_text_to_taint_text(new_template_format, ranges_orig); + std::tuple result = convert_escaped_text_to_taint_text(new_template_format, ranges_orig); StrType result_text = get<0>(result); TaintRangeRefs result_ranges = get<1>(result); PyObject* new_result = new_pyobject_id(result_text.ptr()); diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectFormat.h b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectFormat.h index 7cca00bcddb..c3d1c333f21 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectFormat.h +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectFormat.h @@ -1,9 +1,6 @@ #pragma once #include "Aspects/Helpers.h" #include "Initializer/Initializer.h" -#include "TaintTracking/Source.h" -#include "TaintTracking/TaintRange.h" -#include "TaintTracking/TaintedObject.h" template StrType diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectIndex.cpp b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectIndex.cpp index ec77852e9dc..1b03ce01eaa 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectIndex.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectIndex.cpp @@ -12,10 +12,9 @@ PyObject* index_aspect(PyObject* result_o, PyObject* candidate_text, PyObject* idx, const TaintRangeMapTypePtr& tx_taint_map) { - auto idx_long = PyLong_AsLong(idx); - bool ranges_error; - TaintRangeRefs ranges_to_set, ranges; - std::tie(ranges, ranges_error) = get_ranges(candidate_text, tx_taint_map); + const auto idx_long = PyLong_AsLong(idx); + TaintRangeRefs ranges_to_set; + auto [ranges, ranges_error] = get_ranges(candidate_text, tx_taint_map); if (ranges_error) { return result_o; } @@ -38,7 +37,7 @@ index_aspect(PyObject* result_o, PyObject* candidate_text, PyObject* idx, const } PyObject* -api_index_aspect(PyObject* self, PyObject* const* args, Py_ssize_t nargs) +api_index_aspect(PyObject* self, PyObject* const* args, const Py_ssize_t nargs) { if (nargs != 2) { py::set_error(PyExc_ValueError, MSG_ERROR_N_PARAMS); @@ -49,9 +48,7 @@ api_index_aspect(PyObject* self, PyObject* const* args, Py_ssize_t nargs) PyObject* candidate_text = args[0]; PyObject* idx = args[1]; - PyObject* result_o; - - result_o = PyObject_GetItem(candidate_text, idx); + PyObject* result_o = PyObject_GetItem(candidate_text, idx); if (not ctx_map or ctx_map->empty()) { return result_o; diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectIndex.h b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectIndex.h index 16ad8b487c9..1b1acf43419 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectIndex.h +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectIndex.h @@ -1,5 +1,4 @@ #pragma once -#include "Aspects/Helpers.h" #include "TaintedOps/TaintedOps.h" PyObject* diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectJoin.cpp b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectJoin.cpp index ad3f843e3d7..e190247cebb 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectJoin.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectJoin.cpp @@ -95,8 +95,7 @@ aspect_join(PyObject* sep, PyObject* result, PyObject* iterable_elements, const // b"a".join(u"c", b"d") -> unicode const size_t& element_len = get_pyobject_size(element); if (element_len > 0) { - const auto& to_element = get_tainted_object(element, tx_taint_map); - if (to_element) { + if (const auto& to_element = get_tainted_object(element, tx_taint_map)) { if (current_pos == 0 and !first_tainted_to) { first_tainted_to = to_element; } else { @@ -138,7 +137,7 @@ aspect_join(PyObject* sep, PyObject* result, PyObject* iterable_elements, const } PyObject* -api_join_aspect(PyObject* self, PyObject* const* args, Py_ssize_t nargs) +api_join_aspect(PyObject* self, PyObject* const* args, const Py_ssize_t nargs) { if (nargs != 2) { py::set_error(PyExc_ValueError, MSG_ERROR_N_PARAMS); @@ -152,7 +151,7 @@ api_join_aspect(PyObject* self, PyObject* const* args, Py_ssize_t nargs) if (PyIter_Check(arg0) or PySet_Check(arg0) or PyFrozenSet_Check(arg0)) { PyObject* iterator = PyObject_GetIter(arg0); - if (iterator != NULL) { + if (iterator != nullptr) { PyObject* item; PyObject* list_aux = PyList_New(0); while ((item = PyIter_Next(iterator))) { diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectJoin.h b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectJoin.h index 1349ce2ac4e..9dbab19caa9 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectJoin.h +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectJoin.h @@ -1,7 +1,5 @@ #pragma once #include "Initializer/Initializer.h" -#include "TaintTracking/TaintRange.h" -#include "TaintTracking/TaintedObject.h" namespace py = pybind11; diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectOperatorAdd.cpp b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectOperatorAdd.cpp index af2deef7d0f..8e6cd7398be 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectOperatorAdd.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectOperatorAdd.cpp @@ -16,8 +16,8 @@ add_aspect(PyObject* result_o, PyObject* text_to_add, const TaintRangeMapTypePtr& tx_taint_map) { - size_t len_candidate_text{ get_pyobject_size(candidate_text) }; - size_t len_text_to_add{ get_pyobject_size(text_to_add) }; + const size_t len_candidate_text{ get_pyobject_size(candidate_text) }; + const size_t len_text_to_add{ get_pyobject_size(text_to_add) }; if (len_text_to_add == 0 and len_candidate_text > 0) { return candidate_text; @@ -48,7 +48,7 @@ add_aspect(PyObject* result_o, } auto tainted = initializer->allocate_tainted_object_copy(to_candidate_text); - tainted->add_ranges_shifted(to_text_to_add, (RANGE_START)len_candidate_text); + tainted->add_ranges_shifted(to_text_to_add, static_cast(len_candidate_text)); const auto res_new_id = new_pyobject_id(result_o); Py_DecRef(result_o); set_tainted_object(res_new_id, tainted, tx_taint_map); @@ -80,7 +80,7 @@ api_add_aspect(PyObject* self, PyObject* const* args, Py_ssize_t nargs) PyObject* candidate_text = args[0]; PyObject* text_to_add = args[1]; - PyObject* result_o; + PyObject* result_o = nullptr; if (PyUnicode_Check(candidate_text)) { result_o = PyUnicode_Concat(candidate_text, text_to_add); } else if (PyBytes_Check(candidate_text)) { diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectOperatorAdd.h b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectOperatorAdd.h index 2f51825b899..e6fd4c53e15 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectOperatorAdd.h +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectOperatorAdd.h @@ -1,7 +1,5 @@ #pragma once #include "Initializer/Initializer.h" -#include "TaintTracking/TaintRange.h" -#include "TaintTracking/TaintedObject.h" PyObject* api_add_aspect(PyObject* self, PyObject* const* args, Py_ssize_t nargs); diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectSlice.cpp b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectSlice.cpp index 07a811e9316..3be07f89e54 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectSlice.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectSlice.cpp @@ -8,7 +8,7 @@ * @return A map of taint ranges for the given index range map. */ TaintRangeRefs -reduce_ranges_from_index_range_map(TaintRangeRefs index_range_map) +reduce_ranges_from_index_range_map(const TaintRangeRefs& index_range_map) { TaintRangeRefs new_ranges; TaintRangePtr current_range; @@ -16,8 +16,7 @@ reduce_ranges_from_index_range_map(TaintRangeRefs index_range_map) size_t index; for (index = 0; index < index_range_map.size(); ++index) { - auto taint_range{ index_range_map.at(index) }; - if (taint_range != current_range) { + if (const auto& taint_range{ index_range_map.at(index) }; taint_range != current_range) { if (current_range) { new_ranges.emplace_back( initializer->allocate_taint_range(current_start, index - current_start, current_range->source)); @@ -26,7 +25,7 @@ reduce_ranges_from_index_range_map(TaintRangeRefs index_range_map) current_start = index; } } - if (current_range != NULL) { + if (current_range != nullptr) { new_ranges.emplace_back( initializer->allocate_taint_range(current_start, index - current_start, current_range->source)); } @@ -38,6 +37,9 @@ reduce_ranges_from_index_range_map(TaintRangeRefs index_range_map) * * @param text The text object for which the taint ranges are to be built. * @param ranges The taint range map that stores taint information. + * @param start The start index of the text object. + * @param stop The stop index of the text object. + * @param step The step index of the text object. * * @return A map of taint ranges for the given text object. */ @@ -57,7 +59,7 @@ build_index_range_map(PyObject* text, TaintRangeRefs& ranges, PyObject* start, P index++; } } - long length_text = (long long)py::len(text); + long length_text = static_cast(py::len(text)); while (index < length_text) { index_range_map.emplace_back(nullptr); index++; @@ -71,7 +73,7 @@ build_index_range_map(PyObject* text, TaintRangeRefs& ranges, PyObject* start, P } } long stop_int = length_text; - if (stop != NULL) { + if (stop != nullptr) { stop_int = PyLong_AsLong(stop); if (stop_int > length_text) { stop_int = length_text; @@ -83,7 +85,7 @@ build_index_range_map(PyObject* text, TaintRangeRefs& ranges, PyObject* start, P } } long step_int = 1; - if (step != NULL) { + if (step != nullptr) { step_int = PyLong_AsLong(step); } for (auto i = start_int; i < stop_int; i += step_int) { @@ -101,9 +103,7 @@ slice_aspect(PyObject* result_o, PyObject* candidate_text, PyObject* start, PyOb if (not ctx_map or ctx_map->empty()) { return result_o; } - bool ranges_error; - TaintRangeRefs ranges; - std::tie(ranges, ranges_error) = get_ranges(candidate_text, ctx_map); + auto [ranges, ranges_error] = get_ranges(candidate_text, ctx_map); if (ranges_error or ranges.empty()) { return result_o; } @@ -117,7 +117,7 @@ PyObject* api_slice_aspect(PyObject* self, PyObject* const* args, Py_ssize_t nargs) { if (nargs < 3) { - return NULL; + return nullptr; } PyObject* candidate_text = args[0]; PyObject* start = PyLong_FromLong(0); @@ -125,7 +125,7 @@ api_slice_aspect(PyObject* self, PyObject* const* args, Py_ssize_t nargs) if (PyNumber_Check(args[1])) { start = PyNumber_Long(args[1]); } - PyObject* stop = NULL; + PyObject* stop = nullptr; if (PyNumber_Check(args[2])) { stop = PyNumber_Long(args[2]); } @@ -137,30 +137,30 @@ api_slice_aspect(PyObject* self, PyObject* const* args, Py_ssize_t nargs) } PyObject* slice = PySlice_New(start, stop, step); - if (slice == NULL) { + if (slice == nullptr) { PyErr_Print(); - if (start != NULL) { + if (start != nullptr) { Py_DecRef(start); } - if (stop != NULL) { + if (stop != nullptr) { Py_DecRef(stop); } - if (step != NULL) { + if (step != nullptr) { Py_DecRef(step); } - return NULL; + return nullptr; } PyObject* result = PyObject_GetItem(candidate_text, slice); auto res = slice_aspect(result, candidate_text, start, stop, step); - if (start != NULL) { + if (start != nullptr) { Py_DecRef(start); } - if (stop != NULL) { + if (stop != nullptr) { Py_DecRef(stop); } - if (step != NULL) { + if (step != nullptr) { Py_DecRef(step); } Py_DecRef(slice); diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectSlice.h b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectSlice.h index 8a7b639f221..782561884d9 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectSlice.h +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectSlice.h @@ -1,5 +1,4 @@ #pragma once -#include "Aspects/Helpers.h" #include "TaintedOps/TaintedOps.h" PyObject* diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectSplit.cpp b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectSplit.cpp index aae847e19b2..a503d23162c 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectSplit.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectSplit.cpp @@ -5,16 +5,15 @@ template py::list api_split_text(const StrType& text, const optional& separator, const optional maxsplit) { - auto split = text.attr("split"); - auto split_result = split(separator, maxsplit); + const auto split = text.attr("split"); + const auto split_result = split(separator, maxsplit); const auto tx_map = initializer->get_tainting_map(); if (not tx_map or tx_map->empty()) { return split_result; } - auto ranges = api_get_ranges(text); - if (not ranges.empty()) { + if (auto ranges = api_get_ranges(text); not ranges.empty()) { set_ranges_on_splitted(text, ranges, split_result, tx_map, false); } @@ -25,15 +24,14 @@ template py::list api_rsplit_text(const StrType& text, const optional& separator, const optional maxsplit) { - auto rsplit = text.attr("rsplit"); - auto split_result = rsplit(separator, maxsplit); + const auto rsplit = text.attr("rsplit"); + const auto split_result = rsplit(separator, maxsplit); const auto tx_map = initializer->get_tainting_map(); if (not tx_map or tx_map->empty()) { return split_result; } - auto ranges = api_get_ranges(text); - if (not ranges.empty()) { + if (auto ranges = api_get_ranges(text); not ranges.empty()) { set_ranges_on_splitted(text, ranges, split_result, tx_map, false); } return split_result; @@ -43,15 +41,14 @@ template py::list api_splitlines_text(const StrType& text, bool keepends) { - auto splitlines = text.attr("splitlines"); - auto split_result = splitlines(keepends); + const auto splitlines = text.attr("splitlines"); + const auto split_result = splitlines(keepends); const auto tx_map = initializer->get_tainting_map(); if (not tx_map or tx_map->empty()) { return split_result; } - auto ranges = api_get_ranges(text); - if (not ranges.empty()) { + if (auto ranges = api_get_ranges(text); not ranges.empty()) { set_ranges_on_splitted(text, ranges, split_result, tx_map, keepends); } return split_result; diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectSplit.h b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectSplit.h index 5fb708e7c26..cdaf88a69b8 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectSplit.h +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectSplit.h @@ -4,11 +4,11 @@ template py::list -api_split_text(const StrType& text, const optional& separator, const optional maxsplit); +api_split_text(const StrType& text, const optional& separator, optional maxsplit); template py::list -api_rsplit_text(const StrType& text, const optional& separator, const optional maxsplit); +api_rsplit_text(const StrType& text, const optional& separator, optional maxsplit); template py::list diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectsOsPath.cpp b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectsOsPath.cpp index db40fb0301a..dbc6061f1b7 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectsOsPath.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectsOsPath.cpp @@ -6,7 +6,7 @@ static bool starts_with_separator(const py::handle& arg, const std::string& separator) { - std::string carg = py::cast(arg); + const auto carg = py::cast(arg); return carg.substr(0, 1) == separator; } @@ -14,7 +14,7 @@ template StrType api_ospathjoin_aspect(StrType& first_part, const py::args& args) { - auto ospath = py::module_::import("os.path"); + const auto ospath = py::module_::import("os.path"); auto join = ospath.attr("join"); auto joined = join(first_part, *args); @@ -23,8 +23,8 @@ api_ospathjoin_aspect(StrType& first_part, const py::args& args) return joined; } - std::string separator = ospath.attr("sep").cast(); - auto sepsize = separator.size(); + const auto separator = ospath.attr("sep").cast(); + const auto sepsize = separator.size(); // Find the initial iteration point. This will be the first argument that has the separator ("/foo") // as a first character or first_part (the first element) if no such argument is found. @@ -53,9 +53,7 @@ api_ospathjoin_aspect(StrType& first_part, const py::args& args) if (not root_is_after_first) { // Get the ranges of first_part and set them to the result, skipping the first character position // if it's a separator - bool ranges_error; - TaintRangeRefs ranges; - std::tie(ranges, ranges_error) = get_ranges(first_part.ptr(), tx_map); + auto [ranges, ranges_error] = get_ranges(first_part.ptr(), tx_map); if (not ranges_error and not ranges.empty()) { for (auto& range : ranges) { result_ranges.emplace_back(shift_taint_range(range, current_offset, first_part_len)); @@ -70,17 +68,15 @@ api_ospathjoin_aspect(StrType& first_part, const py::args& args) initial_arg_pos = 0; } - unsigned long unsigned_initial_arg_pos = max(0, initial_arg_pos); + const unsigned long unsigned_initial_arg_pos = max(0, initial_arg_pos); // Now go trough the arguments and do the same for (unsigned long i = 0; i < args.size(); i++) { if (i >= unsigned_initial_arg_pos) { // Set the ranges from the corresponding argument - bool ranges_error; - TaintRangeRefs ranges; - std::tie(ranges, ranges_error) = get_ranges(args[i].ptr(), tx_map); - if (not ranges_error and not ranges.empty()) { - auto len_args_i = py::len(args[i]); + if (auto [ranges, ranges_error] = get_ranges(args[i].ptr(), tx_map); + not ranges_error and not ranges.empty()) { + const auto len_args_i = py::len(args[i]); for (auto& range : ranges) { result_ranges.emplace_back(shift_taint_range(range, current_offset, len_args_i)); } @@ -103,7 +99,7 @@ template StrType api_ospathbasename_aspect(const StrType& path) { - auto ospath = py::module_::import("os.path"); + const auto ospath = py::module_::import("os.path"); auto basename = ospath.attr("basename"); auto basename_result = basename(path); @@ -112,9 +108,7 @@ api_ospathbasename_aspect(const StrType& path) return basename_result; } - bool ranges_error; - TaintRangeRefs ranges; - std::tie(ranges, ranges_error) = get_ranges(path.ptr(), tx_map); + auto [ranges, ranges_error] = get_ranges(path.ptr(), tx_map); if (ranges_error or ranges.empty()) { return basename_result; } @@ -136,7 +130,7 @@ template StrType api_ospathdirname_aspect(const StrType& path) { - auto ospath = py::module_::import("os.path"); + const auto ospath = py::module_::import("os.path"); auto dirname = ospath.attr("dirname"); auto dirname_result = dirname(path); @@ -145,9 +139,7 @@ api_ospathdirname_aspect(const StrType& path) return dirname_result; } - bool ranges_error; - TaintRangeRefs ranges; - std::tie(ranges, ranges_error) = get_ranges(path.ptr(), tx_map); + auto [ranges, ranges_error] = get_ranges(path.ptr(), tx_map); if (ranges_error or ranges.empty()) { return dirname_result; } @@ -167,9 +159,9 @@ api_ospathdirname_aspect(const StrType& path) template static py::tuple -_forward_to_set_ranges_on_splitted(const char* function_name, const StrType& path, bool includeseparator = false) +forward_to_set_ranges_on_splitted(const char* function_name, const StrType& path, bool includeseparator = false) { - auto ospath = py::module_::import("os.path"); + const auto ospath = py::module_::import("os.path"); auto function = ospath.attr(function_name); auto function_result = function(path); @@ -178,9 +170,7 @@ _forward_to_set_ranges_on_splitted(const char* function_name, const StrType& pat return function_result; } - bool ranges_error; - TaintRangeRefs ranges; - std::tie(ranges, ranges_error) = get_ranges(path.ptr(), tx_map); + auto [ranges, ranges_error] = get_ranges(path.ptr(), tx_map); if (ranges_error or ranges.empty()) { return function_result; } @@ -193,35 +183,35 @@ template py::tuple api_ospathsplit_aspect(const StrType& path) { - return _forward_to_set_ranges_on_splitted("split", path); + return forward_to_set_ranges_on_splitted("split", path); } template py::tuple api_ospathsplitext_aspect(const StrType& path) { - return _forward_to_set_ranges_on_splitted("splitext", path, true); + return forward_to_set_ranges_on_splitted("splitext", path, true); } template py::tuple api_ospathsplitdrive_aspect(const StrType& path) { - return _forward_to_set_ranges_on_splitted("splitdrive", path, true); + return forward_to_set_ranges_on_splitted("splitdrive", path, true); } template py::tuple api_ospathsplitroot_aspect(const StrType& path) { - return _forward_to_set_ranges_on_splitted("splitroot", path, true); + return forward_to_set_ranges_on_splitted("splitroot", path, true); } template StrType api_ospathnormcase_aspect(const StrType& path) { - auto ospath = py::module_::import("os.path"); + const auto ospath = py::module_::import("os.path"); auto normcase = ospath.attr("normcase"); auto normcased = normcase(path); @@ -230,16 +220,13 @@ api_ospathnormcase_aspect(const StrType& path) return normcased; } - bool ranges_error; - TaintRangeRefs ranges; - std::tie(ranges, ranges_error) = get_ranges(path.ptr(), tx_map); + auto [ranges, ranges_error] = get_ranges(path.ptr(), tx_map); if (ranges_error or ranges.empty()) { return normcased; } - TaintRangeRefs result_ranges = ranges; - PyObject* new_result = new_pyobject_id(normcased.ptr()); - if (new_result) { + const TaintRangeRefs result_ranges = ranges; + if (PyObject* new_result = new_pyobject_id(normcased.ptr())) { set_ranges(new_result, result_ranges, tx_map); return py::reinterpret_steal(new_result); } diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectsOsPath.h b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectsOsPath.h index 48e1baf3542..ef6bb42c437 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectsOsPath.h +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectsOsPath.h @@ -1,7 +1,5 @@ #pragma once #include "Initializer/Initializer.h" -#include "TaintTracking/TaintRange.h" -#include "TaintTracking/TaintedObject.h" namespace py = pybind11; diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/Helpers.cpp b/ddtrace/appsec/_iast/_taint_tracking/Aspects/Helpers.cpp index b16f1d623bf..926d22fef6f 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/Helpers.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/Helpers.cpp @@ -1,7 +1,6 @@ #include "Helpers.h" #include "Initializer/Initializer.h" #include -#include #include using namespace pybind11::literals; @@ -22,15 +21,13 @@ api_common_replace(const py::str& string_method, const py::args& args, const py::kwargs& kwargs) { - bool ranges_error; - TaintRangeRefs candidate_text_ranges; - StrType res = py::getattr(candidate_text, string_method)(*args, **kwargs); + const StrType res = py::getattr(candidate_text, string_method)(*args, **kwargs); const auto tx_map = initializer->get_tainting_map(); if (not tx_map or tx_map->empty()) { return res; } - std::tie(candidate_text_ranges, ranges_error) = get_ranges(candidate_text.ptr(), tx_map); + auto [candidate_text_ranges, ranges_error] = get_ranges(candidate_text.ptr(), tx_map); if (ranges_error or candidate_text_ranges.empty()) { return res; @@ -76,7 +73,7 @@ mapper_replace(const TaintRangePtr& taint_range, const optional& if (!new_ranges->contains(o)) { return py::none{}; } - TaintRange new_range = py::cast((*new_ranges)[o]); + const TaintRange new_range = py::cast((*new_ranges)[o]); return py::int_(new_range.get_hash()); } @@ -98,26 +95,26 @@ range_sort(const TaintRangePtr& t1, const TaintRangePtr& t2) template StrType -_all_as_formatted_evidence(StrType& text, TagMappingMode tag_mapping_mode) +all_as_formatted_evidence(StrType& text, TagMappingMode tag_mapping_mode) { TaintRangeRefs text_ranges = api_get_ranges(text); - return AsFormattedEvidence(text, text_ranges, tag_mapping_mode, nullopt); + return as_formatted_evidence(text, text_ranges, tag_mapping_mode, nullopt); } template StrType -_int_as_formatted_evidence(StrType& text, TaintRangeRefs text_ranges, TagMappingMode tag_mapping_mode) +int_as_formatted_evidence(StrType& text, TaintRangeRefs text_ranges, TagMappingMode tag_mapping_mode) { - return AsFormattedEvidence(text, text_ranges, tag_mapping_mode, nullopt); + return as_formatted_evidence(text, text_ranges, tag_mapping_mode, nullopt); } // TODO OPTIMIZATION: Remove py::types once this isn't used in Python template StrType -AsFormattedEvidence(StrType& text, - TaintRangeRefs& text_ranges, - const optional& tag_mapping_mode, - const optional& new_ranges) +as_formatted_evidence(StrType& text, + TaintRangeRefs& text_ranges, + const optional& tag_mapping_mode, + const optional& new_ranges) { if (text_ranges.empty()) { return text; @@ -142,9 +139,9 @@ AsFormattedEvidence(StrType& text, // Nothing } } - auto tag = get_tag(content); + const auto tag = get_tag(content); - auto range_end = taint_range->start + taint_range->length; + const auto range_end = taint_range->start + taint_range->length; res_vector.push_back(text[py::slice(py::int_{ index }, py::int_{ taint_range->start }, nullptr)]); res_vector.push_back(StrType(EVIDENCE_MARKS::START_EVIDENCE)); @@ -161,10 +158,10 @@ AsFormattedEvidence(StrType& text, template StrType -ApiAsFormattedEvidence(StrType& text, - optional& text_ranges, - const optional& tag_mapping_mode, - const optional& new_ranges) +api_as_formatted_evidence(StrType& text, + optional& text_ranges, + const optional& tag_mapping_mode, + const optional& new_ranges) { TaintRangeRefs _ranges; if (!text_ranges) { @@ -172,18 +169,17 @@ ApiAsFormattedEvidence(StrType& text, } else { _ranges = text_ranges.value(); } - return AsFormattedEvidence(text, _ranges, tag_mapping_mode, new_ranges); + return as_formatted_evidence(text, _ranges, tag_mapping_mode, new_ranges); } vector split_taints(const string& str_to_split) { - std::regex rgx(R"((:\+-(<[0-9.a-z\-]+>)?|(<[0-9.a-z\-]+>)?-\+:))"); + const std::regex rgx(R"((:\+-(<[0-9.a-z\-]+>)?|(<[0-9.a-z\-]+>)?-\+:))"); std::sregex_token_iterator iter(str_to_split.begin(), str_to_split.end(), rgx, { -1, 0 }); - std::sregex_token_iterator end; vector res; - for (; iter != end; ++iter) { + for (const std::sregex_token_iterator end; iter != end; ++iter) { res.push_back(*iter); } @@ -196,9 +192,9 @@ api_convert_escaped_text_to_taint_text_ba(const py::bytearray& taint_escaped_tex const auto tx_map = initializer->get_tainting_map(); - py::bytes bytes_text = py::bytes() + taint_escaped_text; + const py::bytes bytes_text = py::bytes() + taint_escaped_text; - std::tuple result = _convert_escaped_text_to_taint_text(bytes_text, std::move(ranges_orig)); + const std::tuple result = convert_escaped_text_to_taint_text(bytes_text, std::move(ranges_orig)); PyObject* new_result = new_pyobject_id((py::bytearray() + get<0>(result)).ptr()); set_ranges(new_result, get<1>(result), tx_map); return py::reinterpret_steal(new_result); @@ -210,16 +206,16 @@ api_convert_escaped_text_to_taint_text(const StrType& taint_escaped_text, TaintR { const auto tx_map = initializer->get_tainting_map(); - std::tuple result = _convert_escaped_text_to_taint_text(taint_escaped_text, ranges_orig); + std::tuple result = convert_escaped_text_to_taint_text(taint_escaped_text, ranges_orig); StrType result_text = get<0>(result); - TaintRangeRefs result_ranges = get<1>(result); + const TaintRangeRefs result_ranges = get<1>(result); PyObject* new_result = new_pyobject_id(result_text.ptr()); set_ranges(new_result, result_ranges, tx_map); return py::reinterpret_steal(new_result); } unsigned long int -getNum(std::string s) +getNum(const std::string& s) { unsigned int n = -1; try { @@ -236,9 +232,9 @@ getNum(std::string s) template std::tuple -_convert_escaped_text_to_taint_text(const StrType& taint_escaped_text, TaintRangeRefs ranges_orig) +convert_escaped_text_to_taint_text(const StrType& taint_escaped_text, TaintRangeRefs ranges_orig) { - string result{ u8"" }; + string result; string startswith_element{ ":" }; string taint_escaped_string = py::cast(taint_escaped_text); @@ -256,8 +252,7 @@ _convert_escaped_text_to_taint_text(const StrType& taint_escaped_text, TaintRang string id_evidence; for (string const& element : texts_and_marks) { - bool is_content = index % 2 == 0; - if (is_content) { + if (index % 2 == 0) { result += element; length = py::len(StrType(element)); end += length; @@ -266,8 +261,8 @@ _convert_escaped_text_to_taint_text(const StrType& taint_escaped_text, TaintRang } if (element.rfind(startswith_element, 0) == 0) { id_evidence = element.substr(4, element.length() - 5); - auto range_by_id = get_range_by_hash(getNum(id_evidence), optional_ranges_orig); - if (range_by_id == nullptr) { + if (auto range_by_id = get_range_by_hash(getNum(id_evidence), optional_ranges_orig); + range_by_id == nullptr) { result += element; length = py::len(StrType(element)); end += length; @@ -295,11 +290,11 @@ _convert_escaped_text_to_taint_text(const StrType& taint_escaped_text, TaintRang } id_evidence = element.substr(4, element.length() - 5); start = end; - context_stack.push_back({ id_evidence, start }); + context_stack.emplace_back(id_evidence, start); } else { id_evidence = element.substr(1, element.length() - 5); - auto range_by_id = get_range_by_hash(getNum(id_evidence), optional_ranges_orig); - if (range_by_id == nullptr) { + if (auto range_by_id = get_range_by_hash(getNum(id_evidence), optional_ranges_orig); + range_by_id == nullptr) { result += element; length = py::len(StrType(element)); end += length; @@ -357,7 +352,7 @@ set_ranges_on_splitted(const StrType& source_str, RANGE_START offset = 0; std::string c_source_str = py::cast(source_str); - auto separator_increase = (int)((not include_separator)); + const auto separator_increase = static_cast(not include_separator); for (const auto& item : split_result) { if (not is_text(item.ptr()) or py::len(item) == 0) { @@ -375,12 +370,11 @@ set_ranges_on_splitted(const StrType& source_str, // Find what source_ranges match these positions and create a new range with the start and len updated. for (const auto& range : source_ranges) { - auto range_end_abs = range->start + range->length; - - if (range->start < end && range_end_abs > start) { + if (const auto range_end_abs = range->start + range->length; range->start < end && range_end_abs > start) { // Create a new range with the updated start - auto new_range_start = std::max(range->start - offset, 0L); - auto new_range_length = std::min(end - start, (range->length - std::max(0L, offset - range->start))); + const auto new_range_start = std::max(range->start - offset, 0L); + const auto new_range_length = + std::min(end - start, (range->length - std::max(0L, offset - range->start))); item_ranges.emplace_back( initializer->allocate_taint_range(new_range_start, new_range_length, range->source)); } @@ -419,7 +413,8 @@ parse_params(size_t position, { if (args.size() >= position + 1) { return args[position]; - } else if (kwargs && kwargs.contains(keyword_name)) { + } + if (kwargs && kwargs.contains(keyword_name)) { return kwargs[keyword_name]; } return default_value; @@ -453,32 +448,32 @@ pyexport_aspect_helpers(py::module& m) // cppcheck-suppress assignBoolToPointer "include_separator"_a = false); m.def("_all_as_formatted_evidence", - &_all_as_formatted_evidence, + &all_as_formatted_evidence, "text"_a, "tag_mapping_function"_a = nullopt, py::return_value_policy::move); m.def("_int_as_formatted_evidence", - &_int_as_formatted_evidence, + &int_as_formatted_evidence, "text"_a, "text_ranges"_a = nullopt, "tag_mapping_function"_a = nullopt, py::return_value_policy::move); m.def("as_formatted_evidence", - &ApiAsFormattedEvidence, + &api_as_formatted_evidence, "text"_a, "text_ranges"_a = nullopt, "tag_mapping_function"_a = nullopt, "new_ranges"_a = nullopt, py::return_value_policy::move); m.def("as_formatted_evidence", - &ApiAsFormattedEvidence, + &api_as_formatted_evidence, "text"_a, "text_ranges"_a = nullopt, "tag_mapping_function"_a = nullopt, "new_ranges"_a = nullopt, py::return_value_policy::move); m.def("as_formatted_evidence", - &ApiAsFormattedEvidence, + &api_as_formatted_evidence, "text"_a, "text_ranges"_a = nullopt, "tag_mapping_function"_a = nullopt, diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/Helpers.h b/ddtrace/appsec/_iast/_taint_tracking/Aspects/Helpers.h index b4d4e603da7..7a9504e21a3 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/Helpers.h +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/Helpers.h @@ -3,7 +3,6 @@ #include #include -#include "TaintTracking/Source.h" #include "TaintTracking/TaintRange.h" using namespace pybind11::literals; @@ -21,25 +20,25 @@ api_common_replace(const py::str& string_method, template StrType -_all_as_formatted_evidence(StrType& text, TagMappingMode tag_mapping_mode); +all_as_formatted_evidence(StrType& text, TagMappingMode tag_mapping_mode); template StrType -_int_as_formatted_evidence(StrType& text, TaintRangeRefs text_ranges, TagMappingMode tag_mapping_mode); +int_as_formatted_evidence(StrType& text, TaintRangeRefs text_ranges, TagMappingMode tag_mapping_mode); template StrType -AsFormattedEvidence(StrType& text, - optional& text_ranges, - const TagMappingMode& tag_mapping_mode, - const optional& new_ranges); +as_formatted_evidence(StrType& text, + TaintRangeRefs& text_ranges, + const optional& tag_mapping_mode, + const optional& new_ranges); template StrType -ApiAsFormattedEvidence(StrType& text, - optional& text_ranges, - const optional& tag_mapping_mode, - const optional& new_ranges); +api_as_formatted_evidence(StrType& text, + optional& text_ranges, + const optional& tag_mapping_mode, + const optional& new_ranges); py::bytearray api_convert_escaped_text_to_taint_text_ba(const py::bytearray& taint_escaped_text, TaintRangeRefs ranges_orig); @@ -50,7 +49,7 @@ api_convert_escaped_text_to_taint_text(const StrType& taint_escaped_text, TaintR template std::tuple -_convert_escaped_text_to_taint_text(const StrType& taint_escaped_text, TaintRangeRefs ranges_orig); +convert_escaped_text_to_taint_text(const StrType& taint_escaped_text, TaintRangeRefs ranges_orig); template bool diff --git a/ddtrace/appsec/_iast/_taint_tracking/Initializer/Initializer.cpp b/ddtrace/appsec/_iast/_taint_tracking/Initializer/Initializer.cpp index 8236cbf5503..b1aaee55f81 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Initializer/Initializer.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/Initializer/Initializer.cpp @@ -34,17 +34,16 @@ Initializer::create_tainting_map() void Initializer::clear_tainting_map(const TaintRangeMapTypePtr& tx_map) { - if (not tx_map) + if (not tx_map or tx_map->empty()) return; - auto it = active_map_addreses.find(tx_map.get()); - if (it == active_map_addreses.end()) { + if (const auto it = active_map_addreses.find(tx_map.get()); it == active_map_addreses.end()) { // Map wasn't in the active addresses, do nothing return; } - for (auto& kv_taint_map : *tx_map) { - kv_taint_map.second.second->decref(); + for (const auto& [fst, snd] : *tx_map) { + snd.second->decref(); } tx_map->clear(); @@ -71,8 +70,7 @@ Initializer::clear_tainting_maps() int Initializer::num_objects_tainted() { - auto ctx_map = initializer->get_tainting_map(); - if (ctx_map) { + if (const auto ctx_map = initializer->get_tainting_map()) { return static_cast(ctx_map->size()); } return 0; @@ -81,30 +79,29 @@ Initializer::num_objects_tainted() string Initializer::debug_taint_map() { - auto ctx_map = initializer->get_tainting_map(); + const auto ctx_map = initializer->get_tainting_map(); if (!ctx_map) { return ("[]"); } std::stringstream output; output << "["; - for (const auto& item : *ctx_map) { - output << "{ 'Id-Key': " << item.first << ","; - output << "'Value': { 'Hash': " << item.second.first << ", 'TaintedObject': '" << item.second.second->toString() - << "'}},"; + for (const auto& [fst, snd] : *ctx_map) { + output << "{ 'Id-Key': " << fst << ","; + output << "'Value': { 'Hash': " << snd.first << ", 'TaintedObject': '" << snd.second->toString() << "'}},"; } output << "]"; return output.str(); } int -Initializer::initializer_size() +Initializer::initializer_size() const { return sizeof(*this); } int -Initializer::active_map_addreses_size() +Initializer::active_map_addreses_size() const { return static_cast(active_map_addreses.size()); } @@ -124,7 +121,7 @@ Initializer::allocate_tainted_object() TaintedObjectPtr Initializer::allocate_ranges_into_taint_object(TaintRangeRefs ranges) { - auto toptr = allocate_tainted_object(); + const auto toptr = allocate_tainted_object(); toptr->set_values(std::move(ranges)); return toptr; } @@ -132,7 +129,7 @@ Initializer::allocate_ranges_into_taint_object(TaintRangeRefs ranges) TaintedObjectPtr Initializer::allocate_ranges_into_taint_object_copy(const TaintRangeRefs& ranges) { - auto toptr = allocate_tainted_object(); + const auto toptr = allocate_tainted_object(); toptr->copy_values(ranges); return toptr; } @@ -165,7 +162,7 @@ Initializer::release_tainted_object(TaintedObjectPtr tobj) } TaintRangePtr -Initializer::allocate_taint_range(RANGE_START start, RANGE_LENGTH length, Source origin) +Initializer::allocate_taint_range(const RANGE_START start, const RANGE_LENGTH length, const Source& origin) { if (!available_ranges_stack.empty()) { auto rptr = available_ranges_stack.top(); diff --git a/ddtrace/appsec/_iast/_taint_tracking/Initializer/Initializer.h b/ddtrace/appsec/_iast/_taint_tracking/Initializer/Initializer.h index 8146b7f47fd..204466d9898 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Initializer/Initializer.h +++ b/ddtrace/appsec/_iast/_taint_tracking/Initializer/Initializer.h @@ -71,14 +71,14 @@ class Initializer * * @return The size of the Initializer object. */ - int initializer_size(); + int initializer_size() const; /** * Gets the size of active map addresses. * * @return The size of active map addresses. */ - int active_map_addreses_size(); + int active_map_addreses_size() const; /** * Creates a new taint tracking context. @@ -133,7 +133,7 @@ class Initializer // FIXME: these should be static functions of TaintRange // IMPORTANT: if the returned object is not assigned to the map, you have // responsibility of calling release_taint_range on it or you'll have a leak. - TaintRangePtr allocate_taint_range(RANGE_START start, RANGE_LENGTH length, Source source); + TaintRangePtr allocate_taint_range(RANGE_START start, RANGE_LENGTH length, const Source& source); void release_taint_range(TaintRangePtr rangeptr); }; diff --git a/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/Source.cpp b/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/Source.cpp index ab0960d537a..311647507ba 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/Source.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/Source.cpp @@ -1,9 +1,5 @@ #include -#include -#include -#include - #include "Source.h" using namespace std; @@ -16,7 +12,7 @@ Source::Source(string name, string value, OriginType origin) , origin(origin) {} -Source::Source(int name, string value, OriginType origin) +Source::Source(int name, string value, const OriginType origin) : name(origin_to_str(OriginType{ name })) , value(std::move(value)) , origin(origin) @@ -41,7 +37,7 @@ Source::operator std::string() const int Source::get_hash() const { - return std::hash()(std::hash()(name) ^ (long)origin ^ std::hash()(value)); + return std::hash()(std::hash()(name) ^ static_cast(origin) ^ std::hash()(value)); }; void @@ -77,7 +73,9 @@ pyexport_source(py::module& m) .def_readonly("value", &Source::value) .def("to_string", &Source::toString) .def("__hash__", - [](const Source& self) { return hash{}(self.name + self.value) * (33 + int(self.origin)); }) + [](const Source& self) { + return hash{}(self.name + self.value) * (33 + static_cast(self.origin)); + }) .def("__str__", &Source::toString) .def("__repr__", &Source::toString) .def("__eq__", [](const Source* self, const Source* other) { diff --git a/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/Source.h b/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/Source.h index 8c60587be0e..57a498ac2f9 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/Source.h +++ b/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/Source.h @@ -1,10 +1,5 @@ #pragma once -#include "structmember.h" -#include -#include -#include #include -#include #include "../Constants.h" @@ -46,14 +41,14 @@ struct Source [[nodiscard]] string toString() const; - inline void set_values(string name_ = "", string value_ = "", OriginType origin_ = OriginType()) + void set_values(string name_ = "", string value_ = "", OriginType origin_ = OriginType()) { name = std::move(name_); value = std::move(value_); origin = origin_; } - inline void reset() + void reset() { name = ""; value = ""; @@ -62,7 +57,7 @@ struct Source [[nodiscard]] int get_hash() const; - static inline size_t hash(const string& name, const string& value, const OriginType origin) + static size_t hash(const string& name, const string& value, const OriginType origin) { return std::hash()(std::hash()(name + value) ^ (int)origin); }; @@ -71,7 +66,7 @@ struct Source }; inline string -origin_to_str(OriginType origin_type) +origin_to_str(const OriginType origin_type) { switch (origin_type) { case OriginType::PARAMETER: diff --git a/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/TaintRange.cpp b/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/TaintRange.cpp index 006a71ddfb6..28f612abab1 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/TaintRange.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/TaintRange.cpp @@ -31,26 +31,26 @@ TaintRange::operator std::string() const uint TaintRange::get_hash() const { - uint hstart = hash()(this->start); - uint hlength = hash()(this->length); - uint hsource = hash()(this->source.get_hash()); + const uint hstart = hash()(this->start); + const uint hlength = hash()(this->length); + const uint hsource = hash()(this->source.get_hash()); return hstart ^ hlength ^ hsource; }; TaintRangePtr -shift_taint_range(const TaintRangePtr& source_taint_range, RANGE_START offset, RANGE_LENGTH new_length = -1) +shift_taint_range(const TaintRangePtr& source_taint_range, const RANGE_START offset, const RANGE_LENGTH new_length = -1) { - if (new_length == -1) { - new_length = source_taint_range->length; - } + const auto new_length_to_use = new_length == -1 ? source_taint_range->length : new_length; auto tptr = initializer->allocate_taint_range(source_taint_range->start + offset, // start - new_length, // length + new_length_to_use, // length source_taint_range->source); // origin return tptr; } TaintRangeRefs -shift_taint_ranges(const TaintRangeRefs& source_taint_ranges, RANGE_START offset, RANGE_LENGTH new_length = -1) +shift_taint_ranges(const TaintRangeRefs& source_taint_ranges, + const RANGE_START offset, + const RANGE_LENGTH new_length = -1) { TaintRangeRefs new_ranges; new_ranges.reserve(source_taint_ranges.size()); @@ -62,7 +62,9 @@ shift_taint_ranges(const TaintRangeRefs& source_taint_ranges, RANGE_START offset } TaintRangeRefs -api_shift_taint_ranges(const TaintRangeRefs& source_taint_ranges, RANGE_START offset, RANGE_LENGTH new_length = -1) +api_shift_taint_ranges(const TaintRangeRefs& source_taint_ranges, + const RANGE_START offset, + const RANGE_LENGTH new_length = -1) { return shift_taint_ranges(source_taint_ranges, offset); } @@ -101,7 +103,7 @@ api_set_ranges(py::object& str, const TaintRangeRefs& ranges) * @param nargs The number of arguments in the 'args' array. */ PyObject* -api_set_ranges_from_values(PyObject* self, PyObject* const* args, Py_ssize_t nargs) +api_set_ranges_from_values(PyObject* self, PyObject* const* args, const Py_ssize_t nargs) { bool result = false; const char* result_error_msg = MSG_ERROR_N_PARAMS; @@ -118,15 +120,13 @@ api_set_ranges_from_values(PyObject* self, PyObject* const* args, Py_ssize_t nar pyobject_n = new_pyobject_id(tainted_object); PyObject* len_pyobject_py = args[1]; - long len_pyobject = PyLong_AsLong(len_pyobject_py); - string source_name = PyObjectToString(args[2]); - if (not source_name.empty()) { - string source_value = PyObjectToString(args[3]); - if (not source_value.empty()) { - auto source_origin = OriginType(PyLong_AsLong(args[4])); - auto source = Source(source_name, source_value, source_origin); - auto range = initializer->allocate_taint_range(0, len_pyobject, source); - TaintRangeRefs ranges = vector{ range }; + const long len_pyobject = PyLong_AsLong(len_pyobject_py); + if (const string source_name = PyObjectToString(args[2]); not source_name.empty()) { + if (const string source_value = PyObjectToString(args[3]); not source_value.empty()) { + const auto source_origin = static_cast(PyLong_AsLong(args[4])); + const auto source = Source(source_name, source_value, source_origin); + const auto range = initializer->allocate_taint_range(0, len_pyobject, source); + const auto ranges = vector{ range }; result = set_ranges(pyobject_n, ranges, tx_map); if (not result) { result_error_msg = MSG_ERROR_SET_RANGES; @@ -176,7 +176,7 @@ set_ranges(PyObject* str, const TaintRangeRefs& ranges, const TaintRangeMapTypeP return false; } auto obj_id = get_unique_id(str); - auto it = tx_map->find(obj_id); + const auto it = tx_map->find(obj_id); auto new_tainted_object = initializer->allocate_ranges_into_taint_object(ranges); set_fast_tainted_if_notinterned_unicode(str); @@ -200,22 +200,17 @@ are_all_text_all_ranges(PyObject* candidate_text, const py::tuple& parameter_lis if (not is_text(candidate_text)) return {}; - bool ranges_error; - TaintRangeRefs candidate_text_ranges, all_ranges; + TaintRangeRefs all_ranges; const auto tx_map = initializer->get_tainting_map(); if (not tx_map or tx_map->empty()) { return { {}, {} }; } - std::tie(candidate_text_ranges, ranges_error) = get_ranges(candidate_text, tx_map); + auto [candidate_text_ranges, ranges_error] = get_ranges(candidate_text, tx_map); if (not ranges_error) { for (const auto& param_handler : parameter_list) { - auto param = param_handler.cast().ptr(); - - if (is_text(param)) { - TaintRangeRefs ranges; - std::tie(ranges, ranges_error) = get_ranges(param, tx_map); - if (not ranges_error) { + if (const auto param = param_handler.cast().ptr(); is_text(param)) { + if (auto [ranges, ranges_error] = get_ranges(param, tx_map); not ranges_error) { all_ranges.insert(all_ranges.end(), ranges.begin(), ranges.end()); } } @@ -226,7 +221,7 @@ are_all_text_all_ranges(PyObject* candidate_text, const py::tuple& parameter_lis } TaintRangePtr -get_range_by_hash(size_t range_hash, optional& taint_ranges) +get_range_by_hash(const size_t range_hash, optional& taint_ranges) { if (not taint_ranges or taint_ranges->empty()) { return nullptr; @@ -245,15 +240,13 @@ get_range_by_hash(size_t range_hash, optional& taint_ranges) TaintRangeRefs api_get_ranges(const py::object& string_input) { - bool ranges_error; - TaintRangeRefs ranges; const auto tx_map = initializer->get_tainting_map(); if (not tx_map) { throw py::value_error(MSG_ERROR_TAINT_MAP); } - std::tie(ranges, ranges_error) = get_ranges(string_input.ptr(), tx_map); + auto [ranges, ranges_error] = get_ranges(string_input.ptr(), tx_map); if (ranges_error) { throw py::value_error(MSG_ERROR_GET_RANGES_TYPE); } @@ -263,9 +256,6 @@ api_get_ranges(const py::object& string_input) void api_copy_ranges_from_strings(py::object& str_1, py::object& str_2) { - - bool ranges_error, result; - TaintRangeRefs ranges; const auto tx_map = initializer->get_tainting_map(); if (not tx_map) { @@ -273,34 +263,34 @@ api_copy_ranges_from_strings(py::object& str_1, py::object& str_2) return; } - std::tie(ranges, ranges_error) = get_ranges(str_1.ptr(), tx_map); + auto [ranges, ranges_error] = get_ranges(str_1.ptr(), tx_map); if (ranges_error) { py::set_error(PyExc_TypeError, MSG_ERROR_TAINT_MAP); return; } - result = set_ranges(str_2.ptr(), ranges, tx_map); - if (not result) { + if (const bool result = set_ranges(str_2.ptr(), ranges, tx_map); not result) { py::set_error(PyExc_TypeError, MSG_ERROR_SET_RANGES); } } inline void -api_copy_and_shift_ranges_from_strings(py::object& str_1, py::object& str_2, int offset, int new_length = -1) +api_copy_and_shift_ranges_from_strings(py::object& str_1, + py::object& str_2, + const int offset, + const int new_length = -1) { - bool ranges_error, result; - TaintRangeRefs ranges; const auto tx_map = initializer->get_tainting_map(); if (not tx_map) { py::set_error(PyExc_ValueError, MSG_ERROR_TAINT_MAP); return; } - std::tie(ranges, ranges_error) = get_ranges(str_1.ptr(), tx_map); + auto [ranges, ranges_error] = get_ranges(str_1.ptr(), tx_map); if (ranges_error) { py::set_error(PyExc_TypeError, MSG_ERROR_TAINT_MAP); return; } - result = set_ranges(str_2.ptr(), shift_taint_ranges(ranges, offset, new_length), tx_map); - if (not result) { + if (const bool result = set_ranges(str_2.ptr(), shift_taint_ranges(ranges, offset, new_length), tx_map); + not result) { py::set_error(PyExc_TypeError, MSG_ERROR_SET_RANGES); } } @@ -315,7 +305,7 @@ get_tainted_object(PyObject* str, const TaintRangeMapTypePtr& tx_map) return nullptr; } - auto it = tx_map->find(get_unique_id(str)); + const auto it = tx_map->find(get_unique_id(str)); if (it == tx_map->end()) { return nullptr; } @@ -347,15 +337,14 @@ get_internal_hash(PyObject* obj) } void -set_tainted_object(PyObject* str, TaintedObjectPtr tainted_object, const TaintRangeMapTypePtr& tx_taint_map) +set_tainted_object(PyObject* str, TaintedObjectPtr tainted_object, const TaintRangeMapTypePtr& tx_map) { if (not str or not is_text(str)) { return; } auto obj_id = get_unique_id(str); set_fast_tainted_if_notinterned_unicode(str); - auto it = tx_taint_map->find(obj_id); - if (it != tx_taint_map->end()) { + if (const auto it = tx_map->find(obj_id); it != tx_map->end()) { // The same memory address was probably re-used for a different PyObject, so // we need to overwrite it. if (it->second.second != tainted_object) { @@ -371,7 +360,7 @@ set_tainted_object(PyObject* str, TaintedObjectPtr tainted_object, const TaintRa return; } tainted_object->incref(); - tx_taint_map->insert({ obj_id, std::make_pair(get_internal_hash(str), tainted_object) }); + tx_map->insert({ obj_id, std::make_pair(get_internal_hash(str), tainted_object) }); } // OPTIMIZATION TODO: export the variant of these functions taking a PyObject* @@ -431,8 +420,8 @@ pyexport_taintrange(py::module& m) // Fake constructor, used to force calling allocate_taint_range for performance reasons m.def( "taint_range", - [](RANGE_START start, RANGE_LENGTH length, Source source) { - return initializer->allocate_taint_range(start, length, std::move(source)); + [](const RANGE_START start, const RANGE_LENGTH length, const Source& source) { + return initializer->allocate_taint_range(start, length, source); }, "start"_a, "length"_a, diff --git a/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/TaintRange.h b/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/TaintRange.h index 34186ca53c8..57cacb2a997 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/TaintRange.h +++ b/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/TaintRange.h @@ -1,6 +1,4 @@ #pragma once -#include -#include #include #include @@ -43,7 +41,7 @@ struct TaintRange TaintRange() = default; - TaintRange(RANGE_START start, RANGE_LENGTH length, Source source) + TaintRange(const RANGE_START start, const RANGE_LENGTH length, Source source) : start(start) , length(length) , source(std::move(source)) @@ -53,7 +51,7 @@ struct TaintRange } } - inline void set_values(RANGE_START start_, RANGE_LENGTH length_, Source source_) + inline void set_values(const RANGE_START start_, const RANGE_LENGTH length_, Source source_) { if (length_ <= 0) { throw std::invalid_argument("Error: Length cannot be set to 0."); @@ -79,7 +77,7 @@ TaintRangePtr shift_taint_range(const TaintRangePtr& source_taint_range, RANGE_START offset, RANGE_LENGTH new_length); inline TaintRangePtr -api_shift_taint_range(const TaintRangePtr& source_taint_range, RANGE_START offset, RANGE_LENGTH new_length) +api_shift_taint_range(const TaintRangePtr& source_taint_range, const RANGE_START offset, const RANGE_LENGTH new_length) { return shift_taint_range(source_taint_range, offset, new_length); } @@ -130,7 +128,7 @@ api_set_fast_tainted_if_unicode(const py::object& obj) } inline bool -api_is_unicode_and_not_fast_tainted(const py::object str) +api_is_unicode_and_not_fast_tainted(const py::object& str) { return is_notinterned_notfasttainted_unicode(str.ptr()); } @@ -145,7 +143,7 @@ Py_hash_t get_internal_hash(PyObject* obj); void -set_tainted_object(PyObject* str, TaintedObjectPtr tainted_object, const TaintRangeMapTypePtr& tx_taint_map); +set_tainted_object(PyObject* str, TaintedObjectPtr tainted_object, const TaintRangeMapTypePtr& tx_map); void pyexport_taintrange(py::module& m); diff --git a/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/TaintedObject.cpp b/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/TaintedObject.cpp index bd433c14f53..3a5aa2aa277 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/TaintedObject.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/TaintedObject.cpp @@ -1,7 +1,4 @@ -#include "TaintTracking/TaintedObject.h" #include "Initializer/Initializer.h" -#include "TaintTracking/TaintRange.h" -#include namespace py = pybind11; @@ -16,8 +13,8 @@ namespace py = pybind11; */ TaintRangePtr allocate_limited_taint_range_with_offset(const TaintRangePtr& source_taint_range, - RANGE_START offset, - RANGE_LENGTH max_length) + const RANGE_START offset, + const RANGE_LENGTH max_length) { RANGE_LENGTH length; if (max_length != -1) @@ -33,10 +30,11 @@ allocate_limited_taint_range_with_offset(const TaintRangePtr& source_taint_range /** * @brief Shifts the taint range by the given offset. + * @param source_taint_range The source taint range. * @param offset The offset to be applied. */ TaintRangePtr -shift_taint_range(const TaintRangePtr& source_taint_range, RANGE_START offset) +shift_taint_range(const TaintRangePtr& source_taint_range, const RANGE_START offset) { auto tptr = initializer->allocate_taint_range(source_taint_range->start + offset, // start source_taint_range->length, // length @@ -53,10 +51,10 @@ shift_taint_range(const TaintRangePtr& source_taint_range, RANGE_START offset) * @param orig_offset The offset to be applied at the beginning. */ void -TaintedObject::add_ranges_shifted(TaintedObjectPtr tainted_object, - RANGE_START offset, - RANGE_LENGTH max_length, - RANGE_START orig_offset) +TaintedObject::add_ranges_shifted(const TaintedObjectPtr tainted_object, + const RANGE_START offset, + const RANGE_LENGTH max_length, + const RANGE_START orig_offset) { const auto& ranges = tainted_object->get_ranges(); add_ranges_shifted(ranges, offset, max_length, orig_offset); @@ -72,17 +70,17 @@ TaintedObject::add_ranges_shifted(TaintedObjectPtr tainted_object, */ void TaintedObject::add_ranges_shifted(TaintRangeRefs ranges, - RANGE_START offset, - RANGE_LENGTH max_length, - RANGE_START orig_offset) + const RANGE_START offset, + const RANGE_LENGTH max_length, + const RANGE_START orig_offset) { - const auto to_add = (long)min(ranges.size(), TAINT_RANGE_LIMIT - ranges_.size()); - if (!ranges.empty() and to_add > 0) { + if (const auto to_add = static_cast(min(ranges.size(), TAINT_RANGE_LIMIT - ranges_.size())); + !ranges.empty() and to_add > 0) { ranges_.reserve(ranges_.size() + to_add); - int i = 0; if (offset == 0 and max_length == -1) { ranges_.insert(ranges_.end(), ranges.begin(), ranges.end()); } else { + int i = 0; for (const auto& trange : ranges) { if (max_length != -1 and orig_offset != -1) { // Make sure original position (orig_offset) is covered by the range @@ -104,7 +102,7 @@ TaintedObject::add_ranges_shifted(TaintRangeRefs ranges, } std::string -TaintedObject::toString() +TaintedObject::toString() const { stringstream ss; @@ -121,7 +119,7 @@ TaintedObject::toString() return ss.str(); } -TaintedObject::operator string() +TaintedObject::operator string() const { return toString(); } @@ -166,12 +164,12 @@ void TaintedObject::release() { // If rc_ is negative, there is a bug. - assert(rc_ == 0); + // assert(rc_ == 0); initializer->release_tainted_object(this); } void -pyexport_taintedobject(py::module& m) +pyexport_taintedobject(const py::module& m) { py::class_(m, "TaintedObject").def(py::init<>()); } diff --git a/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/TaintedObject.h b/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/TaintedObject.h index 32985b7e181..a4198424a7c 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/TaintedObject.h +++ b/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/TaintedObject.h @@ -46,9 +46,9 @@ class TaintedObject RANGE_LENGTH max_length = -1, RANGE_START orig_offset = -1); - std::string toString(); + std::string toString() const; - explicit operator string(); + explicit operator string() const; void move_ranges_to_stack(); @@ -62,4 +62,4 @@ class TaintedObject }; void -pyexport_taintedobject(py::module& m); +pyexport_taintedobject(const py::module& m); diff --git a/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/_taint_tracking.h b/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/_taint_tracking.h index 5f4b8159ad1..7a44fe1153a 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/_taint_tracking.h +++ b/ddtrace/appsec/_iast/_taint_tracking/TaintTracking/_taint_tracking.h @@ -1,10 +1,6 @@ #pragma once #include -#include "TaintTracking/Source.h" -#include "TaintTracking/TaintRange.h" -#include "TaintTracking/TaintedObject.h" - inline void pyexport_m_taint_tracking(py::module& m) { diff --git a/ddtrace/appsec/_iast/_taint_tracking/TaintedOps/TaintedOps.cpp b/ddtrace/appsec/_iast/_taint_tracking/TaintedOps/TaintedOps.cpp index 220303fb39c..175d588fc31 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/TaintedOps/TaintedOps.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/TaintedOps/TaintedOps.cpp @@ -1,7 +1,7 @@ #include "TaintedOps/TaintedOps.h" PyObject* -api_new_pyobject_id(PyObject* self, PyObject* const* args, Py_ssize_t nargs) +api_new_pyobject_id(PyObject* self, PyObject* const* args, const Py_ssize_t nargs) { if (nargs != 1 or !args) { throw py::value_error(MSG_ERROR_N_PARAMS); diff --git a/ddtrace/appsec/_iast/_taint_tracking/TaintedOps/TaintedOps.h b/ddtrace/appsec/_iast/_taint_tracking/TaintedOps/TaintedOps.h index 5b3c88506ef..f9592680007 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/TaintedOps/TaintedOps.h +++ b/ddtrace/appsec/_iast/_taint_tracking/TaintedOps/TaintedOps.h @@ -1,10 +1,6 @@ #pragma once #include "Initializer/Initializer.h" -#include "TaintTracking/TaintRange.h" -#include "TaintTracking/TaintedObject.h" #include "Utils/StringUtils.h" -#include -#include using namespace std; using namespace pybind11::literals; diff --git a/ddtrace/appsec/_iast/_taint_tracking/Utils/StringUtils.cpp b/ddtrace/appsec/_iast/_taint_tracking/Utils/StringUtils.cpp index 2878a3631ea..589bd7c18df 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Utils/StringUtils.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/Utils/StringUtils.cpp @@ -10,7 +10,7 @@ using namespace pybind11::literals; using namespace std; -#define _GET_HASH_KEY(hash) (hash & 0xFFFFFF) +#define GET_HASH_KEY(hash) (hash & 0xFFFFFF) typedef struct _PyASCIIObject_State_Hidden { @@ -43,7 +43,7 @@ is_notinterned_notfasttainted_unicode(const PyObject* objptr) } // it cannot be fast tainted if hash is set to -1 (not computed) Py_hash_t hash = ((PyASCIIObject*)objptr)->hash; - return hash == -1 || e->hidden != _GET_HASH_KEY(hash); + return hash == -1 || e->hidden != GET_HASH_KEY(hash); } // For non interned unicode strings, set a hidden mark on it's internsal data @@ -55,13 +55,12 @@ set_fast_tainted_if_notinterned_unicode(PyObject* objptr) if (not objptr or !PyUnicode_Check(objptr) or PyUnicode_CHECK_INTERNED(objptr)) { return; } - auto e = (_PyASCIIObject_State_Hidden*)&(((PyASCIIObject*)objptr)->state); - if (e) { + if (auto e = (_PyASCIIObject_State_Hidden*)&(((PyASCIIObject*)objptr)->state)) { Py_hash_t hash = ((PyASCIIObject*)objptr)->hash; if (hash == -1) { hash = PyObject_Hash(objptr); } - e->hidden = _GET_HASH_KEY(hash); + e->hidden = GET_HASH_KEY(hash); } } @@ -89,18 +88,18 @@ new_pyobject_id(PyObject* tainted_object) } if (PyBytes_Check(tainted_object)) { PyObject* empty_bytes = PyBytes_FromString(""); - auto bytes_join_ptr = py::reinterpret_borrow(empty_bytes).attr("join"); - auto val = Py_BuildValue("(OO)", tainted_object, empty_bytes); - auto res = PyObject_CallFunctionObjArgs(bytes_join_ptr.ptr(), val, NULL); + const auto bytes_join_ptr = py::reinterpret_borrow(empty_bytes).attr("join"); + const auto val = Py_BuildValue("(OO)", tainted_object, empty_bytes); + const auto res = PyObject_CallFunctionObjArgs(bytes_join_ptr.ptr(), val, NULL); Py_DecRef(val); Py_DecRef(empty_bytes); return res; } else if (PyByteArray_Check(tainted_object)) { PyObject* empty_bytes = PyBytes_FromString(""); PyObject* empty_bytearray = PyByteArray_FromObject(empty_bytes); - auto bytearray_join_ptr = py::reinterpret_borrow(empty_bytearray).attr("join"); - auto val = Py_BuildValue("(OO)", tainted_object, empty_bytearray); - auto res = PyObject_CallFunctionObjArgs(bytearray_join_ptr.ptr(), val, NULL); + const auto bytearray_join_ptr = py::reinterpret_borrow(empty_bytearray).attr("join"); + const auto val = Py_BuildValue("(OO)", tainted_object, empty_bytearray); + const auto res = PyObject_CallFunctionObjArgs(bytearray_join_ptr.ptr(), val, NULL); Py_DecRef(val); Py_DecRef(empty_bytes); Py_DecRef(empty_bytearray); diff --git a/ddtrace/appsec/_iast/_taint_tracking/Utils/StringUtils.h b/ddtrace/appsec/_iast/_taint_tracking/Utils/StringUtils.h index 807cef85a2d..80b235170f8 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Utils/StringUtils.h +++ b/ddtrace/appsec/_iast/_taint_tracking/Utils/StringUtils.h @@ -11,7 +11,7 @@ namespace py = pybind11; inline static uintptr_t get_unique_id(const PyObject* str) { - return uintptr_t(str); + return reinterpret_cast(str); } bool diff --git a/ddtrace/appsec/_iast/_taint_tracking/_native.cpp b/ddtrace/appsec/_iast/_taint_tracking/_native.cpp index bbf3bb777ae..c98330ad28a 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/_native.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/_native.cpp @@ -61,7 +61,6 @@ PYBIND11_MODULE(_native, m) const char* env_iast_enabled = std::getenv("DD_IAST_ENABLED"); if (env_iast_enabled == nullptr) { throw py::import_error("IAST not enabled"); - return; } std::string iast_enabled = std::string(env_iast_enabled); @@ -69,7 +68,6 @@ PYBIND11_MODULE(_native, m) iast_enabled.begin(), iast_enabled.end(), iast_enabled.begin(), [](unsigned char c) { return std::tolower(c); }); if (iast_enabled != "true" && iast_enabled != "1") { throw py::import_error("IAST not enabled"); - return; } initializer = make_unique(); From 33b1a9468042908a3ed909a68fa3e61eeece9efe Mon Sep 17 00:00:00 2001 From: Federico Mon Date: Mon, 20 May 2024 14:09:41 +0200 Subject: [PATCH 072/104] fix(iast): fix threading import regression (#9314) Fixes regression introduced in #9307 ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [ ] Title is accurate - [ ] All changes are related to the pull request's stated goal - [ ] Description motivates each change - [ ] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [ ] Testing strategy adequately addresses listed risks - [ ] Change is maintainable (easy to change, telemetry, documentation) - [ ] Release note makes sense to a user of the library - [ ] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [ ] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/appsec/_iast/_overhead_control_engine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddtrace/appsec/_iast/_overhead_control_engine.py b/ddtrace/appsec/_iast/_overhead_control_engine.py index d02015c81b5..2889455749a 100644 --- a/ddtrace/appsec/_iast/_overhead_control_engine.py +++ b/ddtrace/appsec/_iast/_overhead_control_engine.py @@ -4,12 +4,12 @@ (and therefore reduce the overhead to nearly 0) if a certain threshold is reached. """ import os -import threading from typing import Set from typing import Text from typing import Tuple from typing import Type +from ddtrace import _threading as threading from ddtrace._trace.span import Span from ddtrace.internal.logger import get_logger from ddtrace.sampler import RateSampler From 3c03cd0b683c617346e397b16d081f7f93990547 Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Mon, 20 May 2024 09:58:30 -0400 Subject: [PATCH 073/104] chore(ci): update GitHub Actions to latest version (#9296) This helps to avoid 'Node.js 16 actions are deprecated.' messages - checkout v3 -> v4 - setup-python v4 -> v5 - setup-node v3 -> v4 - upload-artifact v3 -> v4 - download-artifact v3 -> v4 ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../workflows/apm-transport-stress-test.yml | 2 +- .github/workflows/build-and-publish-image.yml | 2 +- .github/workflows/build_deploy.yml | 17 +++-- .github/workflows/build_python_3.yml | 14 +++- .github/workflows/changelog.yml | 6 +- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/django-overhead-profile.yml | 6 +- .github/workflows/encoders-profile.yml | 6 +- .github/workflows/flask-overhead-profile.yml | 6 +- .github/workflows/lib-inject-publish.yml | 2 +- .github/workflows/lib-injection.yml | 4 +- .github/workflows/pr-name.yml | 4 +- .github/workflows/requirements-locks.yml | 2 +- .github/workflows/set-target-milestone.yml | 4 +- .github/workflows/system-tests.yml | 20 ++--- .github/workflows/test_frameworks.yml | 76 +++++++++---------- .github/workflows/upstream-issues.yml | 2 +- 17 files changed, 92 insertions(+), 83 deletions(-) diff --git a/.github/workflows/apm-transport-stress-test.yml b/.github/workflows/apm-transport-stress-test.yml index c65fe161c1a..c581629bbe7 100644 --- a/.github/workflows/apm-transport-stress-test.yml +++ b/.github/workflows/apm-transport-stress-test.yml @@ -13,7 +13,7 @@ jobs: DD_API_KEY: ${{ secrets.DD_SHARED_TESTS_API_KEY }} TRACER: python steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: repository: DataDog/apm-transport-stress-tests - name: Build diff --git a/.github/workflows/build-and-publish-image.yml b/.github/workflows/build-and-publish-image.yml index 1fae85cb7e3..b00d5ff19ae 100644 --- a/.github/workflows/build-and-publish-image.yml +++ b/.github/workflows/build-and-publish-image.yml @@ -23,7 +23,7 @@ jobs: build_push: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up QEMU uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx diff --git a/.github/workflows/build_deploy.yml b/.github/workflows/build_deploy.yml index 4a6775f33bf..c915ebace49 100644 --- a/.github/workflows/build_deploy.yml +++ b/.github/workflows/build_deploy.yml @@ -54,12 +54,12 @@ jobs: name: Build source distribution runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # Include all history and tags with: fetch-depth: 0 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 name: Install Python with: python-version: '3.7' @@ -68,8 +68,9 @@ jobs: run: | pip install cython cmake python setup.py sdist - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: + name: source-dist path: dist/*.tar.gz test_alpine_sdist: @@ -80,10 +81,10 @@ jobs: container: image: python:3.9-alpine steps: - - uses: actions/checkout@v3 - - uses: actions/download-artifact@v3 + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 with: - name: artifact + name: source-dist path: dist - name: Install build dependencies @@ -114,10 +115,10 @@ jobs: runs-on: ubuntu-latest if: (github.event_name == 'release' && github.event.action == 'published') steps: - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: - name: artifact path: dist + merge-multiple: true - uses: pypa/gh-action-pypi-publish@release/v1 with: diff --git a/.github/workflows/build_python_3.yml b/.github/workflows/build_python_3.yml index fe6e2279087..feb1c7b564e 100644 --- a/.github/workflows/build_python_3.yml +++ b/.github/workflows/build_python_3.yml @@ -28,12 +28,12 @@ jobs: - os: macos-12 archs: x86_64 universal2 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # Include all history and tags with: fetch-depth: 0 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 if: matrix.os != 'arm-4core-linux' name: Install Python with: @@ -120,6 +120,14 @@ jobs: # DEV: Uncomment to debug MacOS # CIBW_BUILD_VERBOSITY_MACOS: 3 - - uses: actions/upload-artifact@v3 + - if: runner.os != 'Windows' + run: | + echo "ARTIFACT_NAME=${{ matrix.os }}-${{ matrix.archs }}-$(echo "${{ inputs.cibw_build }}" | tr -cd '[:alnum:]_-')" >> $GITHUB_ENV + - if: runner.os == 'Windows' + run: | + chcp 65001 #set code page to utf-8 + echo ("ARTIFACT_NAME=${{ matrix.os }}-${{ matrix.archs }}-${{ inputs.cibw_build }}".replace('*', '').replace(' ', '_')) >> $env:GITHUB_ENV + - uses: actions/upload-artifact@v4 with: + name: wheels-${{ env.ARTIFACT_NAME }} path: ./wheelhouse/*.whl diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 78ff740b118..5f9ac3ec1c7 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -12,7 +12,7 @@ jobs: name: Validate changelog runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # Include all history and tags with: fetch-depth: 0 @@ -25,7 +25,7 @@ jobs: if: github.event_name == 'pull_request' run: scripts/check-releasenotes - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 name: Install Python with: python-version: '3.8' @@ -42,7 +42,7 @@ jobs: rst2html.py CHANGELOG.rst CHANGELOG.html - name: Upload CHANGELOG.rst - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: changelog path: | diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 1703e71210b..4573a7dedf6 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -25,7 +25,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/django-overhead-profile.yml b/.github/workflows/django-overhead-profile.yml index 1ad6fd40fac..f4e52f42874 100644 --- a/.github/workflows/django-overhead-profile.yml +++ b/.github/workflows/django-overhead-profile.yml @@ -16,11 +16,11 @@ jobs: run: working-directory: ddtrace steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: path: ddtrace - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: "3.8" @@ -32,7 +32,7 @@ jobs: run: | bash scripts/profiles/django-simple/run.sh ${PREFIX} - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: django-overhead-profile path: ${{ github.workspace }}/prefix/artifacts diff --git a/.github/workflows/encoders-profile.yml b/.github/workflows/encoders-profile.yml index f2679e0377a..5b2d5d4483c 100644 --- a/.github/workflows/encoders-profile.yml +++ b/.github/workflows/encoders-profile.yml @@ -16,11 +16,11 @@ jobs: run: working-directory: ddtrace steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: path: ddtrace - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: "3.8" @@ -36,7 +36,7 @@ jobs: sed -i 's|${{ github.workspace }}/ddtrace/||g' ${PREFIX}/artifacts/$a done - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: encoders-profile path: ${{ github.workspace }}/prefix/artifacts diff --git a/.github/workflows/flask-overhead-profile.yml b/.github/workflows/flask-overhead-profile.yml index a33a322e8d9..2d6b6a61de7 100644 --- a/.github/workflows/flask-overhead-profile.yml +++ b/.github/workflows/flask-overhead-profile.yml @@ -16,11 +16,11 @@ jobs: run: working-directory: ddtrace steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: path: ddtrace - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: "3.10" @@ -32,7 +32,7 @@ jobs: run: | bash scripts/profiles/flask-simple/run.sh ${PREFIX} - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: flask-overhead-profile path: ${{ github.workspace }}/prefix/artifacts diff --git a/.github/workflows/lib-inject-publish.yml b/.github/workflows/lib-inject-publish.yml index 6d32ca91a6b..ec906b1653f 100644 --- a/.github/workflows/lib-inject-publish.yml +++ b/.github/workflows/lib-inject-publish.yml @@ -17,7 +17,7 @@ jobs: wait_for_package: runs-on: ubuntu-latest steps: - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.11' - name: Wait for package to be available from PyPI diff --git a/.github/workflows/lib-injection.yml b/.github/workflows/lib-injection.yml index 893242f38a8..e1d56d3325e 100644 --- a/.github/workflows/lib-injection.yml +++ b/.github/workflows/lib-injection.yml @@ -37,7 +37,7 @@ jobs: BUILDX_PLATFORMS: linux/amd64 steps: - name: Checkout system tests - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: 'DataDog/system-tests' @@ -74,7 +74,7 @@ jobs: ] fail-fast: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build and run the app run: | SRC="$(pwd)" diff --git a/.github/workflows/pr-name.yml b/.github/workflows/pr-name.yml index da49a45d6e8..5a6e4bdfe80 100644 --- a/.github/workflows/pr-name.yml +++ b/.github/workflows/pr-name.yml @@ -9,10 +9,10 @@ jobs: pr_name_lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 name: Install Node.js with: node-version: 16 diff --git a/.github/workflows/requirements-locks.yml b/.github/workflows/requirements-locks.yml index ce59a0f88e6..613842b7d9f 100644 --- a/.github/workflows/requirements-locks.yml +++ b/.github/workflows/requirements-locks.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest container: ghcr.io/datadog/dd-trace-py/testrunner:latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/.github/workflows/set-target-milestone.yml b/.github/workflows/set-target-milestone.yml index 52441d423f7..6370a0a7d8b 100644 --- a/.github/workflows/set-target-milestone.yml +++ b/.github/workflows/set-target-milestone.yml @@ -12,11 +12,11 @@ jobs: name: Add milestone to merged pull requests runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # Include all history and tags with: fetch-depth: 0 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 name: Install Python with: python-version: '3.8' diff --git a/.github/workflows/system-tests.yml b/.github/workflows/system-tests.yml index b3ece4ea8db..f67b78cb0b6 100644 --- a/.github/workflows/system-tests.yml +++ b/.github/workflows/system-tests.yml @@ -48,13 +48,13 @@ jobs: steps: - name: Setup python 3.9 if: needs.needs-run.outputs.outcome == 'success' || github.event_name == 'schedule' - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.9' - name: Checkout system tests if: needs.needs-run.outputs.outcome == 'success' || github.event_name == 'schedule' - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: 'DataDog/system-tests' @@ -80,7 +80,7 @@ jobs: docker image save system_tests/weblog:latest | gzip > ${{ matrix.weblog-variant}}_weblog_${{ github.sha }}.tar.gz docker image save system_tests/agent:latest | gzip > ${{ matrix.weblog-variant}}_agent_${{ github.sha }}.tar.gz - - uses: actions/upload-artifact@master + - uses: actions/upload-artifact@v4 if: needs.needs-run.outputs.outcome == 'success' || github.event_name == 'schedule' with: name: ${{ matrix.weblog-variant }}_${{ github.sha }} @@ -110,17 +110,17 @@ jobs: steps: - name: Setup python 3.9 if: needs.needs-run.outputs.outcome == 'success' || github.event_name == 'schedule' - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.9' - name: Checkout system tests if: needs.needs-run.outputs.outcome == 'success' || github.event_name == 'schedule' - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: 'DataDog/system-tests' - - uses: actions/download-artifact@master + - uses: actions/download-artifact@v4 if: needs.needs-run.outputs.outcome == 'success' || github.event_name == 'schedule' with: name: ${{ matrix.weblog-variant }}_${{ github.sha }} @@ -229,7 +229,7 @@ jobs: run: tar -czvf artifact.tar.gz $(ls | grep logs) - name: Upload artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: steps.compress-artifact.outcome == 'success' || github.event_name == 'schedule' with: name: logs_${{ matrix.weblog-variant }}_${{ matrix.scenario }} @@ -244,7 +244,7 @@ jobs: steps: - name: Checkout system tests if: needs.needs-run.outputs.outcome == 'success' || github.event_name == 'schedule' - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: 'DataDog/system-tests' - name: Checkout dd-trace-py @@ -254,7 +254,7 @@ jobs: path: 'binaries/dd-trace-py' fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha || github.sha }} - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 if: needs.needs-run.outputs.outcome == 'success' || github.event_name == 'schedule' with: python-version: '3.9' @@ -272,7 +272,7 @@ jobs: run: tar -czvf artifact.tar.gz $(ls | grep logs) - name: Upload artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: needs.needs-run.outputs.outcome == 'success' || github.event_name == 'schedule' with: name: logs_parametric diff --git a/.github/workflows/test_frameworks.yml b/.github/workflows/test_frameworks.yml index 8944eaf38fe..4255f5965d5 100644 --- a/.github/workflows/test_frameworks.yml +++ b/.github/workflows/test_frameworks.yml @@ -16,7 +16,7 @@ jobs: outputs: outcome: ${{ steps.run_needed.outcome }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - id: run_needed name: Check if run is needed run: | @@ -39,15 +39,15 @@ jobs: run: working-directory: bottle steps: - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 if: needs.needs-run.outputs.outcome == 'success' with: python-version: '3.9' - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: path: ddtrace - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: repository: bottlepy/bottle @@ -90,17 +90,17 @@ jobs: # run: # working-directory: sanic # steps: -# - uses: actions/checkout@v3 +# - uses: actions/checkout@v4 # if: needs.needs-run.outputs.outcome == 'success' # with: # path: ddtrace -# - uses: actions/checkout@v3 +# - uses: actions/checkout@v4 # if: needs.needs-run.outputs.outcome == 'success' # with: # repository: sanic-org/sanic # ref: v22.12.0 # path: sanic -# - uses: actions/setup-python@v4 +# - uses: actions/setup-python@v5 # if: needs.needs-run.outputs.outcome == 'success' # with: # python-version: "3.11" @@ -145,17 +145,17 @@ jobs: run: working-directory: django steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: path: ddtrace - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: repository: django/django ref: stable/3.1.x path: django - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 if: needs.needs-run.outputs.outcome == 'success' with: python-version: "3.8" @@ -215,11 +215,11 @@ jobs: run: working-directory: graphene steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: path: ddtrace - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: repository: graphql-python/graphene @@ -227,7 +227,7 @@ jobs: # Unreleased CI fix: https://github.com/graphql-python/graphene/pull/1412 ref: 03277a55123fd2f8a8465c5fa671f7fb0d004c26 path: graphene - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 if: needs.needs-run.outputs.outcome == 'success' with: python-version: "3.9" @@ -262,15 +262,15 @@ jobs: run: working-directory: fastapi steps: - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 if: needs.needs-run.outputs.outcome == 'success' with: python-version: '3.9' - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: path: ddtrace - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: repository: tiangolo/fastapi @@ -311,17 +311,17 @@ jobs: run: working-directory: flask steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: path: ddtrace - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: repository: pallets/flask ref: 1.1.4 path: flask - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 if: needs.needs-run.outputs.outcome == 'success' with: python-version: '3.8' @@ -355,17 +355,17 @@ jobs: run: working-directory: httpx steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: path: ddtrace - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: repository: encode/httpx ref: 0.22.0 path: httpx - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 if: needs.needs-run.outputs.outcome == 'success' with: python-version: '3.9' @@ -403,17 +403,17 @@ jobs: run: working-directory: mako steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: path: ddtrace - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: repository: sqlalchemy/mako ref: rel_1_3_0 path: mako - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 if: needs.needs-run.outputs.outcome == 'success' with: python-version: '3.8' @@ -454,7 +454,7 @@ jobs: run: working-directory: starlette steps: - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 if: needs.needs-run.outputs.outcome == 'success' with: python-version: '3.9' @@ -496,15 +496,15 @@ jobs: run: working-directory: requests steps: - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 if: needs.needs-run.outputs.outcome == 'success' with: python-version: '3.9' - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: path: ddtrace - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: repository: psf/requests @@ -539,15 +539,15 @@ jobs: run: working-directory: asyncpg steps: - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 if: needs.needs-run.outputs.outcome == 'success' with: python-version: '3.9' - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: path: ddtrace - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: repository: magicstack/asyncpg @@ -581,15 +581,15 @@ jobs: run: working-directory: gunicorn steps: - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 if: needs.needs-run.outputs.outcome == 'success' with: python-version: '3.9' - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: path: ddtrace - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: repository: benoitc/gunicorn @@ -618,15 +618,15 @@ jobs: run: working-directory: uwsgi steps: - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 if: needs.needs-run.outputs.outcome == 'success' with: python-version: '3.9' - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: path: ddtrace - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 if: needs.needs-run.outputs.outcome == 'success' with: repository: unbit/uwsgi diff --git a/.github/workflows/upstream-issues.yml b/.github/workflows/upstream-issues.yml index 4a052704ff1..1ea1f31264f 100644 --- a/.github/workflows/upstream-issues.yml +++ b/.github/workflows/upstream-issues.yml @@ -7,7 +7,7 @@ jobs: upstream-issues: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: Kyle-Verhoog/upstream-issue-notifier@v0.1.3 env: GITHUB_TOKEN: ${{ github.token }} From dd076756a53d8d833acb9b1c067114e2bbcb3fc3 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Mon, 20 May 2024 17:24:10 +0200 Subject: [PATCH 074/104] fix: error parsing django cookies (#9299) core.dispatch("django.after_request_headers.post") built a invalid cookie payload ```python def view_insecure_cookies_insecure(request): res = HttpResponse("OK") res.set_cookie("insecure_cookie", "cookie_value", secure=False, httponly=True, samesite="Strict") ``` :x: Before: `{"insecure_cookie": "insecure_cookie=cookie_value; HttpOnly; Path=/; SameSite=Strict"}` :white_check_mark: After: `{"insecure_cookie": "cookie_value; HttpOnly; Path=/; SameSite=Strict"}` or if the cookie was empty ``` def view_insecure_cookies_insecure(request): res = HttpResponse("OK") res.set_cookie("insecure_cookie", "", secure=False, httponly=True, samesite="Strict") ``` :x: Before: `{"insecure_cookie": "insecure_cookie=""; HttpOnly; Path=/; SameSite=Strict"}` :white_check_mark: After: `{"insecure_cookie": "; HttpOnly; Path=/; SameSite=Strict"}` APPSEC-53206 ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. - [x] If change touches code that signs or publishes builds or packages, or handles credentials of any kind, I've requested a review from `@DataDog/security-design-and-guidance`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/contrib/django/utils.py | 19 +++- .../contrib/django/django_app/appsec_urls.py | 37 +++++++ .../contrib/django/test_django_appsec_iast.py | 101 ++++++++++++++++++ 3 files changed, 156 insertions(+), 1 deletion(-) diff --git a/ddtrace/contrib/django/utils.py b/ddtrace/contrib/django/utils.py index 33b5d9d37e1..ed38328a4c6 100644 --- a/ddtrace/contrib/django/utils.py +++ b/ddtrace/contrib/django/utils.py @@ -374,7 +374,24 @@ def _after_request_tags(pin, span: Span, request, response): response_cookies = {} if response.cookies: for k, v in response.cookies.items(): - response_cookies[k] = v.OutputString() + # `v` is a http.cookies.Morsel class instance in some scenarios: + # 'cookie_key=cookie_value; HttpOnly; Path=/; SameSite=Strict' + try: + i = 0 + result = "" + for element in v.OutputString().split(";"): + if i == 0: + # split cookie_key="cookie_value" + key, value = element.split("=", 1) + # Remove quotes "cookie_value" + result = value[1:-1] if value.startswith('"') and value[-1] == '"' else value + else: + result += ";" + element + i += 1 + response_cookies[k] = result + except Exception: + # parse cookies by the old way + response_cookies[k] = v.OutputString() raw_uri = url if raw_uri and request.META.get("QUERY_STRING"): diff --git a/tests/contrib/django/django_app/appsec_urls.py b/tests/contrib/django/django_app/appsec_urls.py index 2d369b1b948..c41a8785cf6 100644 --- a/tests/contrib/django/django_app/appsec_urls.py +++ b/tests/contrib/django/django_app/appsec_urls.py @@ -210,6 +210,38 @@ def sqli_http_request_body(request): return HttpResponse(value, status=200) +def view_insecure_cookies_insecure(request): + res = HttpResponse("OK") + res.set_cookie("insecure", "cookie", secure=False, httponly=True, samesite="Strict") + return res + + +def view_insecure_cookies_secure(request): + res = HttpResponse("OK") + res.set_cookie("secure2", "value", secure=True, httponly=True, samesite="Strict") + return res + + +def view_insecure_cookies_empty(request): + res = HttpResponse("OK") + res.set_cookie("insecure", "", secure=False, httponly=True, samesite="Strict") + return res + + +def view_insecure_cookies_two_insecure_one_secure(request): + res = HttpResponse("OK") + res.set_cookie("insecure1", "cookie1", secure=False, httponly=True, samesite="Strict") + res.set_cookie("insecure2", "cookie2", secure=True, httponly=False, samesite="Strict") + res.set_cookie("secure3", "cookie3", secure=True, httponly=True, samesite="Strict") + return res + + +def view_insecure_cookies_insecure_special_chars(request): + res = HttpResponse("OK") + res.set_cookie("insecure", "cookie?()43jfM;;;===value", secure=False, httponly=True, samesite="Strict") + return res + + def command_injection(request): value = decode_aspect(bytes.decode, 1, request.body) # label iast_command_injection @@ -252,6 +284,11 @@ def validate_querydict(request): handler("sqli_http_request_cookie_name/$", sqli_http_request_cookie_name, name="sqli_http_request_cookie_name"), handler("sqli_http_request_cookie_value/$", sqli_http_request_cookie_value, name="sqli_http_request_cookie_value"), handler("sqli_http_request_body/$", sqli_http_request_body, name="sqli_http_request_body"), + handler("insecure-cookie/test_insecure_2_1/$", view_insecure_cookies_two_insecure_one_secure), + handler("insecure-cookie/test_insecure_special/$", view_insecure_cookies_insecure_special_chars), + handler("insecure-cookie/test_insecure/$", view_insecure_cookies_insecure), + handler("insecure-cookie/test_secure/$", view_insecure_cookies_secure), + handler("insecure-cookie/test_empty_cookie/$", view_insecure_cookies_empty), path( "sqli_http_path_parameter//", sqli_http_path_parameter, diff --git a/tests/contrib/django/test_django_appsec_iast.py b/tests/contrib/django/test_django_appsec_iast.py index f3716c2f799..98039e94d55 100644 --- a/tests/contrib/django/test_django_appsec_iast.py +++ b/tests/contrib/django/test_django_appsec_iast.py @@ -9,6 +9,7 @@ from ddtrace.appsec._iast._utils import _is_python_version_supported as python_supported_by_iast from ddtrace.appsec._iast.constants import VULN_CMDI from ddtrace.appsec._iast.constants import VULN_HEADER_INJECTION +from ddtrace.appsec._iast.constants import VULN_INSECURE_COOKIE from ddtrace.appsec._iast.constants import VULN_SQL_INJECTION from ddtrace.internal.compat import urlencode from ddtrace.settings.asm import config as asm_config @@ -580,3 +581,103 @@ def test_django_header_injection(client, test_spans, tracer): } assert loaded["vulnerabilities"][0]["location"]["line"] == line assert loaded["vulnerabilities"][0]["location"]["path"] == TEST_FILE + + +@pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") +def test_django_insecure_cookie(client, test_spans, tracer): + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)): + oce.reconfigure() + root_span, _ = _aux_appsec_get_root_span( + client, + test_spans, + tracer, + url="/appsec/insecure-cookie/test_insecure/", + ) + + assert root_span.get_metric(IAST.ENABLED) == 1.0 + + loaded = json.loads(root_span.get_tag(IAST.JSON)) + assert loaded["sources"] == [] + assert len(loaded["vulnerabilities"]) == 1 + vulnerability = loaded["vulnerabilities"][0] + assert vulnerability["type"] == VULN_INSECURE_COOKIE + assert vulnerability["evidence"] == {"valueParts": [{"value": "insecure"}]} + assert "path" not in vulnerability["location"].keys() + assert "line" not in vulnerability["location"].keys() + assert vulnerability["location"]["spanId"] + assert vulnerability["hash"] + + +@pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") +def test_django_insecure_cookie_secure(client, test_spans, tracer): + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)): + oce.reconfigure() + root_span, _ = _aux_appsec_get_root_span( + client, + test_spans, + tracer, + url="/appsec/insecure-cookie/test_secure/", + ) + + assert root_span.get_metric(IAST.ENABLED) == 1.0 + + assert root_span.get_tag(IAST.JSON) is None + + +@pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") +def test_django_insecure_cookie_empty_cookie(client, test_spans, tracer): + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)): + oce.reconfigure() + root_span, _ = _aux_appsec_get_root_span( + client, + test_spans, + tracer, + url="/appsec/insecure-cookie/test_empty_cookie/", + ) + + assert root_span.get_metric(IAST.ENABLED) == 1.0 + + assert root_span.get_tag(IAST.JSON) is None + + +@pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") +def test_django_insecure_cookie_2_insecure_1_secure(client, test_spans, tracer): + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)): + oce.reconfigure() + root_span, _ = _aux_appsec_get_root_span( + client, + test_spans, + tracer, + url="/appsec/insecure-cookie/test_insecure_2_1/", + ) + + assert root_span.get_metric(IAST.ENABLED) == 1.0 + + loaded = json.loads(root_span.get_tag(IAST.JSON)) + assert loaded["sources"] == [] + assert len(loaded["vulnerabilities"]) == 2 + + +@pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") +def test_django_insecure_cookie_special_characters(client, test_spans, tracer): + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)): + oce.reconfigure() + root_span, _ = _aux_appsec_get_root_span( + client, + test_spans, + tracer, + url="/appsec/insecure-cookie/test_insecure_special/", + ) + + assert root_span.get_metric(IAST.ENABLED) == 1.0 + + loaded = json.loads(root_span.get_tag(IAST.JSON)) + assert loaded["sources"] == [] + assert len(loaded["vulnerabilities"]) == 1 + vulnerability = loaded["vulnerabilities"][0] + assert vulnerability["type"] == VULN_INSECURE_COOKIE + assert vulnerability["evidence"] == {"valueParts": [{"value": "insecure"}]} + assert "path" not in vulnerability["location"].keys() + assert "line" not in vulnerability["location"].keys() + assert vulnerability["location"]["spanId"] + assert vulnerability["hash"] From 182bede4417672d50dcd3cea9c69164e29744817 Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Mon, 20 May 2024 12:27:54 -0400 Subject: [PATCH 075/104] chore(ci): add rust toolchain to ci environments (#9235) Not currently being used, but will be needed in the future. ## Testing These are the necessary edits I've need on #9232 in order to get CI to be green for building the package in CircleCI and cibuildwheel. ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [ ] Title is accurate - [ ] All changes are related to the pull request's stated goal - [ ] Description motivates each change - [ ] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [ ] Testing strategy adequately addresses listed risks - [ ] Change is maintainable (easy to change, telemetry, documentation) - [ ] Release note makes sense to a user of the library - [ ] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [ ] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .circleci/config.templ.yml | 8 ++++++++ .github/workflows/build_deploy.yml | 6 ++---- .github/workflows/build_python_3.yml | 18 ++++++++++++++++++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/.circleci/config.templ.yml b/.circleci/config.templ.yml index 5bb7d942be0..fb49b013331 100644 --- a/.circleci/config.templ.yml +++ b/.circleci/config.templ.yml @@ -81,6 +81,12 @@ commands: # Make sure we install and run riot on Python 3 - run: pip3 install riot==0.19.0 + setup_rust: + description: "Install rust toolchain" + steps: + - run: curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain stable -y + - run: echo 'export PATH="$HOME/.cargo/bin:$PATH"' >> "$BASH_ENV" + setup_hatch: description: "Install hatch" steps: @@ -1324,6 +1330,7 @@ jobs: executor: python310 steps: - checkout + - setup_rust - setup_hatch - run: hatch run slotscheck:_ @@ -1331,6 +1338,7 @@ jobs: executor: python310 steps: - checkout + - setup_rust - setup_hatch - run: hatch run meta-testing:meta-testing diff --git a/.github/workflows/build_deploy.yml b/.github/workflows/build_deploy.yml index c915ebace49..9618297a852 100644 --- a/.github/workflows/build_deploy.yml +++ b/.github/workflows/build_deploy.yml @@ -48,7 +48,6 @@ jobs: uses: ./.github/workflows/build_python_3.yml with: cibw_build: 'cp312*' - cibw_prerelease_pythons: 'True' build_sdist: name: Build source distribution @@ -58,15 +57,14 @@ jobs: # Include all history and tags with: fetch-depth: 0 - + - uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions/setup-python@v5 name: Install Python with: python-version: '3.7' - - name: Build sdist run: | - pip install cython cmake + pip install "setuptools_scm[toml]>=4" "cython" "cmake>=3.24.2,<3.28" "setuptools-rust" python setup.py sdist - uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/build_python_3.yml b/.github/workflows/build_python_3.yml index feb1c7b564e..eb3d0e21863 100644 --- a/.github/workflows/build_python_3.yml +++ b/.github/workflows/build_python_3.yml @@ -69,6 +69,15 @@ jobs: CIBW_SKIP: ${{ inputs.cibw_skip }} CIBW_PRERELEASE_PYTHONS: ${{ inputs.cibw_prerelease_pythons }} CMAKE_BUILD_PARALLEL_LEVEL: 12 + CIBW_MUSLLINUX_I686_IMAGE: ghcr.io/datadog/dd-trace-py/pypa_musllinux_1_2_i686:latest + CIBW_BEFORE_ALL: > + if [[ "$(uname -m)-$(uname -i)-$(uname -o | tr '[:upper:]' '[:lower:]')-$(ldd --version 2>&1 | head -n 1 | awk '{print $1}')" != "i686-unknown-linux-musl" ]]; + then + curl -sSf https://sh.rustup.rs | sh -s -- -y; + fi + CIBW_BEFORE_ALL_WINDOWS: rustup target add i686-pc-windows-msvc + CIBW_BEFORE_ALL_MACOS: rustup target add aarch64-apple-darwin + CIBW_ENVIRONMENT_LINUX: "PATH=$HOME/.cargo/bin:$PATH" CIBW_REPAIR_WHEEL_COMMAND_LINUX: | mkdir ./tempwheelhouse && unzip -l {wheel} | grep '\.so' && @@ -100,6 +109,15 @@ jobs: CIBW_SKIP: ${{ inputs.cibw_skip }} CIBW_PRERELEASE_PYTHONS: ${{ inputs.cibw_prerelease_pythons }} CMAKE_BUILD_PARALLEL_LEVEL: 12 + CIBW_MUSLLINUX_I686_IMAGE: ghcr.io/datadog/dd-trace-py/pypa_musllinux_1_2_i686:latest + CIBW_BEFORE_ALL: > + if [[ "$(uname -m)-$(uname -i)-$(uname -o | tr '[:upper:]' '[:lower:]')-$(ldd --version 2>&1 | head -n 1 | awk '{print $1}')" != "i686-unknown-linux-musl" ]]; + then + curl -sSf https://sh.rustup.rs | sh -s -- -y; + fi + CIBW_BEFORE_ALL_WINDOWS: rustup target add i686-pc-windows-msvc + CIBW_BEFORE_ALL_MACOS: rustup target add aarch64-apple-darwin + CIBW_ENVIRONMENT_LINUX: "PATH=$HOME/.cargo/bin:$PATH" CIBW_REPAIR_WHEEL_COMMAND_LINUX: | mkdir ./tempwheelhouse && unzip -l {wheel} | grep '\.so' && From f6ab59728c3708288646bd8752c91f83cfd9ba87 Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Mon, 20 May 2024 13:11:17 -0400 Subject: [PATCH 076/104] chore(ci): add custom pypa_musllinux_1_2_i686 image with Rust compiler (#9319) We need to get i686-unknown-linux-musl compatible rust compiler into the pypa i686 musl image for use with cibuildwheel in order to support compiler rust extensions. ## Testing The PR originally built and published the image from this PR. This resulted in the following image being created: https://github.com/DataDog/dd-trace-py/pkgs/container/dd-trace-py%2Fpypa_musllinux_1_2_i686 This image was tested locally to ensure ddtrace would build properly: ``` $ docker run --rm -it ghcr.io/datadog/dd-trace-py/pypa_musllinux_1_2_i686:latest /bin/sh > git clone https://github.com/DataDog/dd-trace-py.git > cd ./dd-trace-py # Check out branch which has a Rust extension > git checkout brettlangdon/core.rate_limiter > python3.12 -m pip install . ``` This was also tested by updating the `build` job for the PR #9232, showing it working as expected with cibuildwheel. ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [ ] Title is accurate - [ ] All changes are related to the pull request's stated goal - [ ] Description motivates each change - [ ] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [ ] Testing strategy adequately addresses listed risks - [ ] Change is maintainable (easy to change, telemetry, documentation) - [ ] Release note makes sense to a user of the library - [ ] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [ ] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .github/workflows/build-and-publish-image.yml | 5 +++++ .github/workflows/pypa_musllinux_1_2_i686.yml | 21 +++++++++++++++++++ docker/Dockerfile.pypa_musllinux_1_2_i686 | 3 +++ 3 files changed, 29 insertions(+) create mode 100644 .github/workflows/pypa_musllinux_1_2_i686.yml create mode 100644 docker/Dockerfile.pypa_musllinux_1_2_i686 diff --git a/.github/workflows/build-and-publish-image.yml b/.github/workflows/build-and-publish-image.yml index b00d5ff19ae..d015d0c5c3f 100644 --- a/.github/workflows/build-and-publish-image.yml +++ b/.github/workflows/build-and-publish-image.yml @@ -15,6 +15,10 @@ on: context: required: true type: string + file: + required: false + type: string + default: Dockerfile secrets: token: required: true @@ -42,3 +46,4 @@ jobs: platforms: ${{ inputs.platforms }} build-args: ${{ inputs.build-args }} context: ${{ inputs.context }} + file: ${{ inputs.context }}/${{ inputs.file }} diff --git a/.github/workflows/pypa_musllinux_1_2_i686.yml b/.github/workflows/pypa_musllinux_1_2_i686.yml new file mode 100644 index 00000000000..601f0fbff51 --- /dev/null +++ b/.github/workflows/pypa_musllinux_1_2_i686.yml @@ -0,0 +1,21 @@ +name: PyPA i686 musl linux CI image + +on: + workflow_dispatch: + push: + branches: + - 'main' + paths: + - 'docker/**' + +jobs: + build-and-publish: + uses: ./.github/workflows/build-and-publish-image.yml + with: + tags: 'ghcr.io/datadog/dd-trace-py/pypa_musllinux_1_2_i686:${{ github.sha }},ghcr.io/datadog/dd-trace-py/pypa_musllinux_1_2_i686:latest' + platforms: 'linux/386' + build-args: '' + context: ./docker + file: Dockerfile.pypa_musllinux_1_2_i686 + secrets: + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/docker/Dockerfile.pypa_musllinux_1_2_i686 b/docker/Dockerfile.pypa_musllinux_1_2_i686 new file mode 100644 index 00000000000..612901bae45 --- /dev/null +++ b/docker/Dockerfile.pypa_musllinux_1_2_i686 @@ -0,0 +1,3 @@ +FROM quay.io/pypa/musllinux_1_2_i686:latest +RUN apk add --no-cache gcc libgcc libstdc++ llvm15-libs musl musl-dev rust-stdlib && \ + apk add --no-cache rust cargo From c045d04eef2bfccffe26454b142d1553cb03d7b0 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Mon, 20 May 2024 19:27:00 +0100 Subject: [PATCH 077/104] chore(di): fix infinite loop in exception replay (#9317) The change that added third-party code detection to exception replay introduced a potential infinite loop. We change the coding to avoid that scenario. ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [ ] Title is accurate - [ ] All changes are related to the pull request's stated goal - [ ] Description motivates each change - [ ] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [ ] Testing strategy adequately addresses listed risks - [ ] Change is maintainable (easy to change, telemetry, documentation) - [ ] Release note makes sense to a user of the library - [ ] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [ ] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../debugging/_exception/auto_instrument.py | 54 +++++++++---------- .../exception/test_auto_instrument.py | 3 ++ 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/ddtrace/debugging/_exception/auto_instrument.py b/ddtrace/debugging/_exception/auto_instrument.py index 36a4bbbe354..7b153647198 100644 --- a/ddtrace/debugging/_exception/auto_instrument.py +++ b/ddtrace/debugging/_exception/auto_instrument.py @@ -178,34 +178,32 @@ def on_span_finish(self, span: Span) -> None: code = frame.f_code seq_nr = next(seq) - if not is_user_code(Path(frame.f_code.co_filename)): - continue - - snapshot_id = frame.f_locals.get(SNAPSHOT_KEY, None) - if snapshot_id is None: - # We don't have a snapshot for the frame so we create one - snapshot = SpanExceptionSnapshot( - probe=SpanExceptionProbe.build(exc_id, frame), - frame=frame, - thread=current_thread(), - trace_context=span, - exc_id=exc_id, - ) - - # Capture - snapshot.line() - - # Collect - self.collector.push(snapshot) - - # Memoize - frame.f_locals[SNAPSHOT_KEY] = snapshot_id = snapshot.uuid - - # Add correlation tags on the span - span.set_tag_str(FRAME_SNAPSHOT_ID_TAG % seq_nr, snapshot_id) - span.set_tag_str(FRAME_FUNCTION_TAG % seq_nr, code.co_name) - span.set_tag_str(FRAME_FILE_TAG % seq_nr, code.co_filename) - span.set_tag_str(FRAME_LINE_TAG % seq_nr, str(_tb.tb_lineno)) + if is_user_code(Path(frame.f_code.co_filename)): + snapshot_id = frame.f_locals.get(SNAPSHOT_KEY, None) + if snapshot_id is None: + # We don't have a snapshot for the frame so we create one + snapshot = SpanExceptionSnapshot( + probe=SpanExceptionProbe.build(exc_id, frame), + frame=frame, + thread=current_thread(), + trace_context=span, + exc_id=exc_id, + ) + + # Capture + snapshot.line() + + # Collect + self.collector.push(snapshot) + + # Memoize + frame.f_locals[SNAPSHOT_KEY] = snapshot_id = snapshot.uuid + + # Add correlation tags on the span + span.set_tag_str(FRAME_SNAPSHOT_ID_TAG % seq_nr, snapshot_id) + span.set_tag_str(FRAME_FUNCTION_TAG % seq_nr, code.co_name) + span.set_tag_str(FRAME_FILE_TAG % seq_nr, code.co_filename) + span.set_tag_str(FRAME_LINE_TAG % seq_nr, str(_tb.tb_lineno)) # Move up the stack _tb = _tb.tb_next diff --git a/tests/debugging/exception/test_auto_instrument.py b/tests/debugging/exception/test_auto_instrument.py index 7717e746e89..53cee4b88d8 100644 --- a/tests/debugging/exception/test_auto_instrument.py +++ b/tests/debugging/exception/test_auto_instrument.py @@ -4,6 +4,7 @@ import ddtrace import ddtrace.debugging._exception.auto_instrument as auto_instrument +from ddtrace.internal.packages import _third_party_packages from ddtrace.internal.rate_limiter import BudgetRateLimiterWithJitter as RateLimiter from tests.debugging.mocking import exception_debugging from tests.utils import TracerTestCase @@ -24,8 +25,10 @@ def setUp(self): super(ExceptionDebuggingTestCase, self).setUp() self.backup_tracer = ddtrace.tracer ddtrace.tracer = self.tracer + _third_party_packages().remove("ddtrace") def tearDown(self): + _third_party_packages().add("ddtrace") ddtrace.tracer = self.backup_tracer super(ExceptionDebuggingTestCase, self).tearDown() From 02c34dc09b1117d5dc46cabe0fa06cbe7577d959 Mon Sep 17 00:00:00 2001 From: Federico Mon Date: Tue, 21 May 2024 09:41:08 +0200 Subject: [PATCH 078/104] ci: grouped macrobenchmarks (#9305) More macrobenchmarks, grouped with a name for better discovery in UI ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Co-authored-by: Brett Langdon --- .gitlab/macrobenchmarks.yml | 102 +++++++++++++++++++++++++++--------- 1 file changed, 78 insertions(+), 24 deletions(-) diff --git a/.gitlab/macrobenchmarks.yml b/.gitlab/macrobenchmarks.yml index 69d65339ac2..93d67f26ad2 100644 --- a/.gitlab/macrobenchmarks.yml +++ b/.gitlab/macrobenchmarks.yml @@ -55,32 +55,86 @@ variables: # benchmarks get changed to run on every PR) allow_failure: true -macrobenchmarks: +baseline: extends: .macrobenchmarks - parallel: - matrix: - - DD_BENCHMARKS_CONFIGURATION: baseline - BP_PYTHON_SCENARIO_DIR: flask-realworld - DDTRACE_INSTALL_VERSION: "git+https://github.com/Datadog/dd-trace-py@${CI_COMMIT_SHA}" + variables: + DD_BENCHMARKS_CONFIGURATION: baseline + BP_PYTHON_SCENARIO_DIR: flask-realworld + DDTRACE_INSTALL_VERSION: "git+https://github.com/Datadog/dd-trace-py@${CI_COMMIT_SHA}" + + +tracing-rc-disabled-telemetry-disabled: + extends: .macrobenchmarks + variables: + DD_BENCHMARKS_CONFIGURATION: only-tracing + BP_PYTHON_SCENARIO_DIR: flask-realworld + DDTRACE_INSTALL_VERSION: "git+https://github.com/Datadog/dd-trace-py@${CI_COMMIT_SHA}" + DD_REMOTE_CONFIGURATION_ENABLED: "false" + DD_INSTRUMENTATION_TELEMETRY_ENABLED: "false" + +tracing-rc-enabled-telemetry-disabled: + extends: .macrobenchmarks + variables: + DD_BENCHMARKS_CONFIGURATION: only-tracing + BP_PYTHON_SCENARIO_DIR: flask-realworld + DDTRACE_INSTALL_VERSION: "git+https://github.com/Datadog/dd-trace-py@${CI_COMMIT_SHA}" + DD_REMOTE_CONFIGURATION_ENABLED: "true" + DD_INSTRUMENTATION_TELEMETRY_ENABLED: "false" + +tracing-rc-disabled-telemetry-enabled: + extends: .macrobenchmarks + variables: + DD_BENCHMARKS_CONFIGURATION: only-tracing + BP_PYTHON_SCENARIO_DIR: flask-realworld + DDTRACE_INSTALL_VERSION: "git+https://github.com/Datadog/dd-trace-py@${CI_COMMIT_SHA}" + DD_REMOTE_CONFIGURATION_ENABLED: "false" + DD_INSTRUMENTATION_TELEMETRY_ENABLED: "true" - - DD_BENCHMARKS_CONFIGURATION: only-tracing - BP_PYTHON_SCENARIO_DIR: flask-realworld - DDTRACE_INSTALL_VERSION: "git+https://github.com/Datadog/dd-trace-py@${CI_COMMIT_SHA}" +tracing-rc-enabled-telemetry-enabled: + extends: .macrobenchmarks + variables: + DD_BENCHMARKS_CONFIGURATION: only-tracing + BP_PYTHON_SCENARIO_DIR: flask-realworld + DDTRACE_INSTALL_VERSION: "git+https://github.com/Datadog/dd-trace-py@${CI_COMMIT_SHA}" + DD_REMOTE_CONFIGURATION_ENABLED: "true" + DD_INSTRUMENTATION_TELEMETRY_ENABLED: "true" - - DD_BENCHMARKS_CONFIGURATION: only-tracing - BP_PYTHON_SCENARIO_DIR: flask-realworld - DDTRACE_INSTALL_VERSION: "git+https://github.com/Datadog/dd-trace-py@${CI_COMMIT_SHA}" - DD_REMOTE_CONFIGURATION_ENABLED: "false" - DD_INSTRUMENTATION_TELEMETRY_ENABLED: "true" +appsec-enabled-iast-disabled-ep-disabled: + extends: .macrobenchmarks + variables: + DD_BENCHMARKS_CONFIGURATION: only-tracing + BP_PYTHON_SCENARIO_DIR: flask-realworld + DDTRACE_INSTALL_VERSION: "git+https://github.com/Datadog/dd-trace-py@${CI_COMMIT_SHA}" + DD_APPSEC_ENABLED: "true" + DD_IAST_ENABLED: "false" + DD_APPSEC_RASP_ENABLED: "false" - - DD_BENCHMARKS_CONFIGURATION: only-tracing - BP_PYTHON_SCENARIO_DIR: flask-realworld - DDTRACE_INSTALL_VERSION: "git+https://github.com/Datadog/dd-trace-py@${CI_COMMIT_SHA}" - DD_REMOTE_CONFIGURATION_ENABLED: "false" - DD_INSTRUMENTATION_TELEMETRY_ENABLED: "false" +appsec-disabled-iast-enabled-ep-disabled: + extends: .macrobenchmarks + variables: + DD_BENCHMARKS_CONFIGURATION: only-tracing + BP_PYTHON_SCENARIO_DIR: flask-realworld + DDTRACE_INSTALL_VERSION: "git+https://github.com/Datadog/dd-trace-py@${CI_COMMIT_SHA}" + DD_APPSEC_ENABLED: "false" + DD_IAST_ENABLED: "true" + DD_APPSEC_RASP_ENABLED: "false" + +appsec-enabled-iast-enabled-ep-disabled: + extends: .macrobenchmarks + variables: + DD_BENCHMARKS_CONFIGURATION: only-tracing + BP_PYTHON_SCENARIO_DIR: flask-realworld + DDTRACE_INSTALL_VERSION: "git+https://github.com/Datadog/dd-trace-py@${CI_COMMIT_SHA}" + DD_APPSEC_ENABLED: "true" + DD_IAST_ENABLED: "true" + DD_APPSEC_RASP_ENABLED: "false" - - DD_BENCHMARKS_CONFIGURATION: only-tracing - BP_PYTHON_SCENARIO_DIR: flask-realworld - DDTRACE_INSTALL_VERSION: "git+https://github.com/Datadog/dd-trace-py@${CI_COMMIT_SHA}" - DD_REMOTE_CONFIGURATION_ENABLED: "true" - DD_INSTRUMENTATION_TELEMETRY_ENABLED: "true" +appsec-enabled-iast-disabled-ep-enabled: + extends: .macrobenchmarks + variables: + DD_BENCHMARKS_CONFIGURATION: only-tracing + BP_PYTHON_SCENARIO_DIR: flask-realworld + DDTRACE_INSTALL_VERSION: "git+https://github.com/Datadog/dd-trace-py@${CI_COMMIT_SHA}" + DD_APPSEC_ENABLED: "true" + DD_IAST_ENABLED: "false" + DD_APPSEC_RASP_ENABLED: "true" From 9e9c81acbd8852abd93ef513494ddb2dc9cafed3 Mon Sep 17 00:00:00 2001 From: Juanjo Alvarez Martinez Date: Tue, 21 May 2024 10:32:09 +0200 Subject: [PATCH 079/104] chore: ignore django test for patching (#9320) ## Description While a new PR with a fix for the `terminate` calls caused by uncatched exceptions coming from pybind11 is coming, this adds the urlreverse Django's test to the ignore list (which is needed anyway because the `assertRaises` of a testcase eats any exceptions so the `join_aspect` can't catch it and call the original function). ## Checklist - [X] Change(s) are motivated and described in the PR description - [X] Testing strategy is described if automated tests are not included in the PR - [X] Risks are described (performance impact, potential for breakage, maintainability) - [X] Change is maintainable (easy to change, telemetry, documentation) - [X] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [X] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [X] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [X] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [ ] Title is accurate - [ ] All changes are related to the pull request's stated goal - [ ] Description motivates each change - [ ] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [ ] Testing strategy adequately addresses listed risks - [ ] Change is maintainable (easy to change, telemetry, documentation) - [ ] Release note makes sense to a user of the library - [ ] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [ ] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Signed-off-by: Juanjo Alvarez --- ddtrace/appsec/_iast/_ast/ast_patching.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ddtrace/appsec/_iast/_ast/ast_patching.py b/ddtrace/appsec/_iast/_ast/ast_patching.py index 2ff3d8027e2..bb40650850b 100644 --- a/ddtrace/appsec/_iast/_ast/ast_patching.py +++ b/ddtrace/appsec/_iast/_ast/ast_patching.py @@ -37,6 +37,7 @@ "pytest", # Testing framework "freezegun", # Testing utilities for time manipulation "sklearn", # Machine learning library + "urlpatterns_reverse.tests", # assertRaises eat exceptions in native code, so we don't call the original function ) From c5cf56f57aea7bf3ec0fa13618e3d2b66d706db4 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Tue, 21 May 2024 11:29:26 +0100 Subject: [PATCH 080/104] chore(internal): bytecode wrapping context (#9222) We introduce a new mechanism of wrapping functions via a special context manager that is capable of capturing return values as well. The goal is to allow observability into the called functions, to have access to local variables on exit. This approach has the extra benefit of not introducing any extra frames in the call stack of the wrapped function. ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [ ] Title is accurate - [ ] All changes are related to the pull request's stated goal - [ ] Description motivates each change - [ ] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [ ] Testing strategy adequately addresses listed risks - [ ] Change is maintainable (easy to change, telemetry, documentation) - [ ] Release note makes sense to a user of the library - [ ] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [ ] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/internal/assembly.py | 3 + ddtrace/internal/wrapping/context.py | 687 +++++++++++++++++++++++++++ tests/internal/test_wrapping.py | 305 ++++++++++++ 3 files changed, 995 insertions(+) create mode 100644 ddtrace/internal/wrapping/context.py diff --git a/ddtrace/internal/assembly.py b/ddtrace/internal/assembly.py index 9d502995a00..c1740192540 100644 --- a/ddtrace/internal/assembly.py +++ b/ddtrace/internal/assembly.py @@ -272,3 +272,6 @@ def dis(self) -> None: def __iter__(self) -> t.Iterator[bc.Instr]: return iter(self._instrs) + + def __len__(self) -> int: + return len(self._instrs) diff --git a/ddtrace/internal/wrapping/context.py b/ddtrace/internal/wrapping/context.py new file mode 100644 index 00000000000..24fd91d483e --- /dev/null +++ b/ddtrace/internal/wrapping/context.py @@ -0,0 +1,687 @@ +from contextvars import ContextVar +from inspect import iscoroutinefunction +import sys +from types import FrameType +from types import FunctionType +from types import TracebackType +import typing as t + + +try: + from typing import Protocol # noqa:F401 +except ImportError: + from typing_extensions import Protocol # type: ignore[assignment] + +import bytecode +from bytecode import Bytecode + +from ddtrace.internal.assembly import Assembly + + +T = t.TypeVar("T") + +# This module implements utilities for wrapping a function with a context +# manager. The rough idea is to re-write the function's bytecode to look like +# this: +# +# def foo(): +# with wrapping_context: +# # Original function code +# +# Because we also want to capture the return value, our context manager extends +# the Python one by implementing a __return__ method that will be called with +# the return value of the function. The __exit__ method is only called if the +# function raises an exception. +# +# Because CPython 3.11 introduced zero-cost exceptions, we cannot nest try +# blocks in the function's bytecode. In this case, we call the context manager +# methods directly at the right places, and set up the appropriate exception +# handling code. For older versions of Python we rely on the with statement to +# perform entry and exit operations. Calls to __return__ are explicit in all +# cases. +# +# Some advantages of wrapping a function this way are: +# - Access to the local variables on entry and on return/exit via the frame +# object. +# - No intermediate function calls that pollute the call stack. +# - No need to call the wrapped function manually. +# +# The actual bytecode wrapping is performed once on a target function via a +# universal wrapping context. Multiple context wrapping of a function is allowed +# and it is virtually implemented on top of the concrete universal wrapping +# context. This makes multiple wrapping/unwrapping easy, as it translates to a +# single bytecode wrapping/unwrapping operation. +# +# Context wrappers should be implemented as subclasses of the WrappingContext +# class. The __priority__ attribute can be used to control the order in which +# multiple context wrappers are entered and exited. The __enter__ and __exit__ +# methods should be implemented to perform the necessary operations. The +# __exit__ method is called if the wrapped function raises an exception. The +# frame of the wrapped function can be accessed via the __frame__ property. The +# __return__ method can be implemented to capture the return value of the +# wrapped function. If implemented, its return value will be used as the wrapped +# function return value. The wrapped function can be accessed via the +# __wrapped__ attribute. Context-specific values can be stored and retrieved +# with the set and get methods. + +CONTEXT_HEAD = Assembly() +CONTEXT_RETURN = Assembly() +CONTEXT_FOOT = Assembly() + +if sys.version_info >= (3, 12): + CONTEXT_HEAD.parse( + r""" + push_null + load_const {context_enter} + call 0 + pop_top + """ + ) + + CONTEXT_RETURN.parse( + r""" + load_const {context_return} + push_null + swap 3 + call 1 + """ + ) + + CONTEXT_RETURN_CONST = Assembly() + CONTEXT_RETURN_CONST.parse( + r""" + push_null + load_const {context_return} + load_const {value} + call 1 + """ + ) + + CONTEXT_FOOT.parse( + r""" + try @_except lasti + push_exc_info + push_null + load_const {context_exit} + call 0 + pop_top + reraise 2 + tried + + _except: + copy 3 + pop_except + reraise 1 + """ + ) + + +elif sys.version_info >= (3, 11): + CONTEXT_HEAD.parse( + r""" + push_null + load_const {context_enter} + precall 0 + call 0 + pop_top + """ + ) + + CONTEXT_RETURN.parse( + r""" + load_const {context_return} + push_null + swap 3 + precall 1 + call 1 + """ + ) + + CONTEXT_EXC_HEAD = Assembly() + CONTEXT_EXC_HEAD.parse( + r""" + push_null + load_const {context_exit} + precall 0 + call 0 + pop_top + """ + ) + + CONTEXT_FOOT.parse( + r""" + try @_except lasti + push_exc_info + push_null + load_const {context_exit} + precall 0 + call 0 + pop_top + reraise 2 + tried + + _except: + copy 3 + pop_except + reraise 1 + """ + ) + +elif sys.version_info >= (3, 10): + CONTEXT_HEAD.parse( + r""" + load_const {context} + setup_with @_except + pop_top + _except: + """ + ) + + CONTEXT_RETURN.parse( + r""" + load_const {context} + load_method $__return__ + rot_three + rot_three + call_method 1 + """ + ) + + CONTEXT_FOOT.parse( + r""" + with_except_start + pop_top + reraise 1 + """ + ) + +elif sys.version_info >= (3, 9): + CONTEXT_HEAD.parse( + r""" + load_const {context} + setup_with @_except + pop_top + _except: + """ + ) + + CONTEXT_RETURN.parse( + r""" + load_const {context} + load_method $__return__ + rot_three + rot_three + call_method 1 + """ + ) + + CONTEXT_FOOT.parse( + r""" + with_except_start + pop_top + reraise + """ + ) + + +elif sys.version_info >= (3, 7): + CONTEXT_HEAD.parse( + r""" + load_const {context} + setup_with @_except + pop_top + _except: + """ + ) + + CONTEXT_RETURN.parse( + r""" + load_const {context} + load_method $__return__ + rot_three + rot_three + call_method 1 + """ + ) + + CONTEXT_FOOT.parse( + r""" + with_cleanup_start + with_cleanup_finish + end_finally + load_const None + return_value + """ + ) + + +# This is abstract and should not be used directly +class BaseWrappingContext(t.ContextManager): + __priority__: int = 0 + + def __init__(self, f: FunctionType): + self.__wrapped__ = f + self._storage_stack: ContextVar[list[dict]] = ContextVar(f"{type(self).__name__}__storage_stack", default=[]) + + def __enter__(self) -> "BaseWrappingContext": + self._storage_stack.get().append({}) + return self + + def _pop_storage(self) -> t.Dict[str, t.Any]: + return self._storage_stack.get().pop() + + def __return__(self, value: T) -> T: + self._pop_storage() + return value + + def __exit__( + self, + exc_type: t.Optional[t.Type[BaseException]], + exc_val: t.Optional[BaseException], + exc_tb: t.Optional[TracebackType], + ) -> None: + self._pop_storage() + + def get(self, key: str) -> t.Any: + return self._storage_stack.get()[-1][key] + + def set(self, key: str, value: T) -> T: + self._storage_stack.get()[-1][key] = value + return value + + @classmethod + def wrapped(cls, f: FunctionType) -> "BaseWrappingContext": + if cls.is_wrapped(f): + context = cls.extract(f) + assert isinstance(context, cls) # nosec + else: + context = cls(f) + context.wrap() + return context + + @classmethod + def is_wrapped(cls, _f: FunctionType) -> bool: + raise NotImplementedError + + @classmethod + def extract(cls, _f: FunctionType) -> "BaseWrappingContext": + raise NotImplementedError + + def wrap(self) -> None: + raise NotImplementedError + + def unwrap(self) -> None: + raise NotImplementedError + + +# This is the public interface exported by this module +class WrappingContext(BaseWrappingContext): + @property + def __frame__(self) -> FrameType: + try: + return _UniversalWrappingContext.extract(self.__wrapped__).get("__frame__") + except ValueError: + raise AttributeError("Wrapping context not entered") + + def get_local(self, name: str) -> t.Any: + return self.__frame__.f_locals[name] + + @classmethod + def is_wrapped(cls, f: FunctionType) -> bool: + try: + return bool(cls.extract(f)) + except ValueError: + return False + + @classmethod + def extract(cls, f: FunctionType) -> "WrappingContext": + if _UniversalWrappingContext.is_wrapped(f): + try: + return _UniversalWrappingContext.extract(f).registered(cls) + except KeyError: + pass + msg = f"Function is not wrapped with {cls}" + raise ValueError(msg) + + def wrap(self) -> None: + t.cast(_UniversalWrappingContext, _UniversalWrappingContext.wrapped(self.__wrapped__)).register(self) + + def unwrap(self) -> None: + f = self.__wrapped__ + + if _UniversalWrappingContext.is_wrapped(f): + _UniversalWrappingContext.extract(f).unregister(self) + + +class ContextWrappedFunction(Protocol): + """A wrapped function.""" + + __dd_context_wrapped__ = None # type: t.Optional[_UniversalWrappingContext] + + def __call__(self, *args, **kwargs): + pass + + +# This class provides an interface between single bytecode wrapping and multiple +# logical context wrapping +class _UniversalWrappingContext(BaseWrappingContext): + def __init__(self, f: FunctionType) -> None: + super().__init__(f) + + self._contexts: t.List[WrappingContext] = [] + + def register(self, context: WrappingContext) -> None: + _type = type(context) + if any(isinstance(c, _type) for c in self._contexts): + raise ValueError("Context already registered") + + self._contexts.append(context) + self._contexts.sort(key=lambda c: c.__priority__) + + def unregister(self, context: WrappingContext) -> None: + try: + self._contexts.remove(context) + except ValueError: + raise ValueError("Context not registered") + + if not self._contexts: + self.unwrap() + + def is_registered(self, context: WrappingContext) -> bool: + return type(context) in self._contexts + + def registered(self, context_type: t.Type[WrappingContext]) -> WrappingContext: + for context in self._contexts: + if isinstance(context, context_type): + return context + raise KeyError(f"Context {context_type} not registered") + + def __enter__(self) -> "_UniversalWrappingContext": + super().__enter__() + + # Make the frame object available to the contexts + self.set("__frame__", sys._getframe(1)) + + for context in self._contexts: + context.__enter__() + + return self + + def _exit(self) -> None: + self.__exit__(*sys.exc_info()) + + def __exit__(self, *exc) -> None: + if exc == (None, None, None): + # In Python 3.7 this gets called when the context manager is exited + # normally + return + + for context in self._contexts[::-1]: + context.__exit__(*exc) + + super().__exit__(*exc) + + def __return__(self, value: T) -> T: + for context in self._contexts[::-1]: + context.__return__(value) + + return super().__return__(value) + + @classmethod + def is_wrapped(cls, f: FunctionType) -> bool: + return hasattr(f, "__dd_context_wrapped__") + + @classmethod + def extract(cls, f: FunctionType) -> "_UniversalWrappingContext": + if not cls.is_wrapped(f): + raise ValueError("Function is not wrapped") + return t.cast(_UniversalWrappingContext, t.cast(ContextWrappedFunction, f).__dd_context_wrapped__) + + if sys.version_info >= (3, 11): + + def wrap(self) -> None: + f = self.__wrapped__ + + if self.is_wrapped(f): + raise ValueError("Function already wrapped") + + bc = Bytecode.from_code(f.__code__) + + # Prefix every return + i = 0 + while i < len(bc): + instr = bc[i] + try: + if instr.name == "RETURN_VALUE": + return_code = CONTEXT_RETURN.bind({"context_return": self.__return__}, lineno=instr.lineno) + elif sys.version_info >= (3, 12) and instr.name == "RETURN_CONST": # Python 3.12+ + return_code = CONTEXT_RETURN_CONST.bind( + {"context_return": self.__return__, "value": instr.arg}, lineno=instr.lineno + ) + else: + return_code = [] + + bc[i:i] = return_code + i += len(return_code) + except AttributeError: + # Not an instruction + pass + i += 1 + + # Search for the RESUME instruction + for i, instr in enumerate(bc, 1): + try: + if instr.name == "RESUME": + break + except AttributeError: + # Not an instruction + pass + else: + i = 0 + + bc[i:i] = CONTEXT_HEAD.bind({"context_enter": self.__enter__}, lineno=f.__code__.co_firstlineno) + + # Wrap every line outside a try block + except_label = bytecode.Label() + first_try_begin = last_try_begin = bytecode.TryBegin(except_label, push_lasti=True) + + i = 0 + while i < len(bc): + instr = bc[i] + if isinstance(instr, bytecode.TryBegin) and last_try_begin is not None: + bc.insert(i, bytecode.TryEnd(last_try_begin)) + last_try_begin = None + i += 1 + elif isinstance(instr, bytecode.TryEnd): + j = i + 1 + while j < len(bc) and not isinstance(bc[j], bytecode.TryBegin): + if isinstance(bc[j], bytecode.Instr): + last_try_begin = bytecode.TryBegin(except_label, push_lasti=True) + bc.insert(i + 1, last_try_begin) + break + j += 1 + i += 1 + i += 1 + + bc.insert(0, first_try_begin) + + bc.append(bytecode.TryEnd(last_try_begin)) + bc.append(except_label) + bc.extend(CONTEXT_FOOT.bind({"context_exit": self._exit})) + + # Mark the function as wrapped by a wrapping context + t.cast(ContextWrappedFunction, f).__dd_context_wrapped__ = self + + # Replace the function code with the wrapped code + f.__code__ = bc.to_code() + + def unwrap(self) -> None: + f = self.__wrapped__ + + if not self.is_wrapped(f): + return + + wrapped = t.cast(ContextWrappedFunction, f) + + bc = Bytecode.from_code(f.__code__) + + # Remove the exception handling code + bc[-len(CONTEXT_FOOT) :] = [] + bc.pop() + bc.pop() + + except_label = bc.pop(0).target + + # Remove the try blocks + i = 0 + while i < len(bc): + instr = bc[i] + if isinstance(instr, bytecode.TryBegin) and instr.target is except_label: + bc.pop(i) + elif isinstance(instr, bytecode.TryEnd) and instr.entry.target is except_label: + bc.pop(i) + else: + i += 1 + + # Remove the head of the try block + wc = wrapped.__dd_context_wrapped__ + for i, instr in enumerate(bc): + try: + if instr.name == "LOAD_CONST" and instr.arg is wc: + break + except AttributeError: + # Not an instruction + pass + + # Search for the RESUME instruction + for i, instr in enumerate(bc, 1): + try: + if instr.name == "RESUME": + break + except AttributeError: + # Not an instruction + pass + else: + i = 0 + + bc[i : i + len(CONTEXT_HEAD)] = [] + + # Un-prefix every return + i = 0 + while i < len(bc): + instr = bc[i] + try: + if instr.name == "RETURN_VALUE": + return_code = CONTEXT_RETURN + elif sys.version_info >= (3, 12) and instr.name == "RETURN_CONST": # Python 3.12+ + return_code = CONTEXT_RETURN_CONST + else: + return_code = None + + if return_code is not None: + bc[i - len(return_code) : i] = [] + i -= len(return_code) + except AttributeError: + # Not an instruction + pass + i += 1 + + # Recreate the code object + f.__code__ = bc.to_code() + + # Remove the wrapping context marker + del wrapped.__dd_context_wrapped__ + + else: + + def wrap(self) -> None: + f = self.__wrapped__ + + if self.is_wrapped(f): + raise ValueError("Function already wrapped") + + bc = Bytecode.from_code(f.__code__) + + # Prefix every return + i = 0 + while i < len(bc): + instr = bc[i] + try: + if instr.name == "RETURN_VALUE": + return_code = CONTEXT_RETURN.bind({"context": self}, lineno=instr.lineno) + else: + return_code = [] + + bc[i:i] = return_code + i += len(return_code) + except AttributeError: + # Not an instruction + pass + i += 1 + + # Search for the GEN_START instruction + i = 0 + if sys.version_info >= (3, 10) and iscoroutinefunction(f): + for i, instr in enumerate(bc, 1): + try: + if instr.name == "GEN_START": + break + except AttributeError: + # Not an instruction + pass + + *bc[i:i], except_label = CONTEXT_HEAD.bind({"context": self}, lineno=f.__code__.co_firstlineno) + + bc.append(except_label) + bc.extend(CONTEXT_FOOT.bind()) + + # Mark the function as wrapped by a wrapping context + t.cast(ContextWrappedFunction, f).__dd_context_wrapped__ = self + + # Replace the function code with the wrapped code + f.__code__ = bc.to_code() + + def unwrap(self) -> None: + f = self.__wrapped__ + + if not self.is_wrapped(f): + return + + wrapped = t.cast(ContextWrappedFunction, f) + + bc = Bytecode.from_code(f.__code__) + + # Remove the exception handling code + bc[-len(CONTEXT_FOOT) :] = [] + bc.pop() + + # Remove the head of the try block + wc = wrapped.__dd_context_wrapped__ + for i, instr in enumerate(bc): + try: + if instr.name == "LOAD_CONST" and instr.arg is wc: + break + except AttributeError: + # Not an instruction + pass + + bc[i : i + len(CONTEXT_HEAD) - 1] = [] + + # Remove all the return handlers + i = 0 + while i < len(bc): + instr = bc[i] + try: + if instr.name == "RETURN_VALUE": + bc[i - len(CONTEXT_RETURN) : i] = [] + i -= len(CONTEXT_RETURN) + except AttributeError: + # Not an instruction + pass + i += 1 + + # Recreate the code object + f.__code__ = bc.to_code() + + # Remove the wrapping context marker + del wrapped.__dd_context_wrapped__ diff --git a/tests/internal/test_wrapping.py b/tests/internal/test_wrapping.py index 3f7a73b19ef..d27eadac43b 100644 --- a/tests/internal/test_wrapping.py +++ b/tests/internal/test_wrapping.py @@ -1,3 +1,4 @@ +import asyncio from contextlib import asynccontextmanager import inspect import sys @@ -7,6 +8,8 @@ from ddtrace.internal.wrapping import unwrap from ddtrace.internal.wrapping import wrap +from ddtrace.internal.wrapping.context import WrappingContext +from ddtrace.internal.wrapping.context import _UniversalWrappingContext def assert_stack(expected): @@ -491,3 +494,305 @@ def f(a, b, c=None): assert closure(1, 2, 3) == (1, 2, 3, 42) assert channel == [((42,), {}), closure, ((1, 2, 3), {}), (1, 2, 3, 42)] + + +NOTSET = object() + + +class DummyWrappingContext(WrappingContext): + def __init__(self, f): + super().__init__(f) + + self.entered = False + self.exited = False + self.return_value = NOTSET + self.exc_info = None + self.frame = None + + def __enter__(self): + self.entered = True + self.frame = self.__frame__ + return super().__enter__() + + def __exit__(self, exc_type, exc_value, traceback): + self.exited = True + if exc_value is not None: + self.exc_info = (exc_type, exc_value, traceback) + super().__exit__(exc_type, exc_value, traceback) + + def __return__(self, value): + self.return_value = value + return super().__return__(value) + + +def test_wrapping_context_happy(): + def foo(): + return 42 + + wc = DummyWrappingContext(foo) + wc.wrap() + + assert foo() == 42 + + assert wc.entered + assert wc.return_value == 42 + assert not wc.exited + assert wc.exc_info is None + + assert wc.frame.f_code.co_name == "foo" + assert wc.frame.f_code.co_filename == __file__ + + +def test_wrapping_context_unwrapping(): + def foo(): + return 42 + + wc = DummyWrappingContext(foo) + wc.wrap() + assert _UniversalWrappingContext.is_wrapped(foo) + + wc.unwrap() + assert not _UniversalWrappingContext.is_wrapped(foo) + + assert foo() == 42 + + assert not wc.entered + assert wc.return_value is NOTSET + assert not wc.exited + assert wc.exc_info is None + + +def test_wrapping_context_exc(): + def foo(): + raise ValueError("foo") + + wc = DummyWrappingContext(foo) + wc.wrap() + + with pytest.raises(ValueError): + foo() + + assert wc.entered + assert wc.return_value is NOTSET + assert wc.exited + + _type, exc, _ = wc.exc_info + assert _type == ValueError + assert exc.args == ("foo",) + + +def test_wrapping_context_exc_on_exit(): + class BrokenExitWrappingContext(DummyWrappingContext): + def __exit__(self, exc_type, exc_value, traceback): + super().__exit__(exc_type, exc_value, traceback) + raise RuntimeError("broken") + + def foo(): + raise ValueError("foo") + + wc = BrokenExitWrappingContext(foo) + wc.wrap() + + with pytest.raises(RuntimeError): + foo() + + assert wc.entered + assert wc.return_value is NOTSET + assert wc.exited + + _type, exc, _ = wc.exc_info + assert _type == ValueError + assert exc.args == ("foo",) + + +def test_wrapping_context_priority(): + class HighPriorityWrappingContext(DummyWrappingContext): + def __enter__(self): + nonlocal mutated + + mutated = True + + return super().__enter__() + + def __return__(self, value): + nonlocal mutated + + assert not mutated + + return super().__return__(value) + + class LowPriorityWrappingContext(DummyWrappingContext): + __priority__ = 99 + + def __enter__(self): + nonlocal mutated + + assert mutated + + return super().__enter__() + + def __return__(self, value): + nonlocal mutated + + mutated = False + + return super().__return__(value) + + mutated = False + + def foo(): + return 42 + + hwc = HighPriorityWrappingContext(foo) + lwc = LowPriorityWrappingContext(foo) + + # Wrap low first. We want to make sure that hwc is entered first + lwc.wrap() + hwc.wrap() + + foo() + + assert lwc.entered + assert hwc.return_value == 42 + + +def test_wrapping_context_recursive(): + values = [] + + class RecursiveWrappingContext(DummyWrappingContext): + def __enter__(self): + nonlocal values + super().__enter__() + + n = self.__frame__.f_locals["n"] + self.set("n", n) + values.append(n) + + return self + + def __return__(self, value): + nonlocal values + n = self.__frame__.f_locals["n"] + assert self.get("n") == n + values.append(n) + + return super().__return__(value) + + def factorial(n): + if n == 0: + return 1 + return n * factorial(n - 1) + + wc = RecursiveWrappingContext(factorial) + wc.wrap() + + assert factorial(5) == 120 + assert values == [5, 4, 3, 2, 1, 0, 0, 1, 2, 3, 4, 5] + + +def test_wrapping_context_generator(): + def foo(): + yield from range(10) + return 42 + + wc = DummyWrappingContext(foo) + wc.wrap() + + assert list(foo()) == list(range(10)) + + assert wc.entered + assert wc.return_value == 42 + assert not wc.exited + assert wc.exc_info is None + + +@pytest.mark.asyncio +async def test_wrapping_context_async_generator(): + async def arange(count): + for i in range(count): + yield (i) + await asyncio.sleep(0.0) + + wc = DummyWrappingContext(arange) + wc.wrap() + + a = [] + async for _ in arange(10): + a.append(_) + + assert a == list(range(10)) + + assert wc.entered + assert wc.return_value is None + assert not wc.exited + assert wc.exc_info is None + + +@pytest.mark.asyncio +async def test_wrapping_context_async_happy() -> None: + async def coro(): + return 1 + + wc = DummyWrappingContext(coro) + wc.wrap() + + assert await coro() == 1 + + assert wc.entered + assert wc.return_value == 1 + assert not wc.exited + assert wc.exc_info is None + + +@pytest.mark.asyncio +async def test_wrapping_context_async_exc() -> None: + async def coro(): + raise ValueError("foo") + + wc = DummyWrappingContext(coro) + wc.wrap() + + with pytest.raises(ValueError): + await coro() + + assert wc.entered + assert wc.return_value is NOTSET + assert wc.exited + + _type, exc, _ = wc.exc_info + assert _type is ValueError + assert exc.args == ("foo",) + + +@pytest.mark.asyncio +async def test_wrapping_context_async_concurrent() -> None: + values = [] + + class ConcurrentWrappingContext(DummyWrappingContext): + def __enter__(self): + super().__enter__() + + self.set("n", self.__frame__.f_locals["n"]) + + return self + + def __return__(self, value): + nonlocal values + + values.append((self.get("n"), self.__frame__.f_locals["n"])) + + return super().__return__(value) + + async def fibonacci(n): + if n <= 1: + return 1 + return sum(await asyncio.gather(fibonacci(n - 1), fibonacci(n - 2))) + + wc = ConcurrentWrappingContext(fibonacci) + wc.wrap() + + N = 20 + + await asyncio.gather(*[fibonacci(n) for n in range(1, N)]) + + assert set(values) == {(n, n) for n in range(0, N)} From 6e46390b29dd6dac2c17e41c199c73f3f47b3d52 Mon Sep 17 00:00:00 2001 From: Alexander S Date: Tue, 21 May 2024 14:09:53 +0300 Subject: [PATCH 081/104] chore(debugger): consolidate pii redaction keys for all libraries (#9148) ## Checklist Consolidate PII redaction keys for all libraries. ## Notes Related PRs: `dotnet` - https://github.com/DataDog/dd-trace-dotnet/pull/5522 `java` - https://github.com/DataDog/dd-trace-java/pull/6980 - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/debugging/_redaction.py | 8 +++++--- tests/debugging/exception/test_auto_instrument.py | 5 +++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/ddtrace/debugging/_redaction.py b/ddtrace/debugging/_redaction.py index 86106f15d97..7dd84bcc320 100644 --- a/ddtrace/debugging/_redaction.py +++ b/ddtrace/debugging/_redaction.py @@ -15,6 +15,7 @@ { "2fa", "accesstoken", + "address", "aiohttpsession", "apikey", "apisecret", @@ -30,6 +31,7 @@ "clientid", "clientsecret", "config", + "connectionstring", "connectsid", "cookie", "credentials", @@ -42,7 +44,9 @@ "encryptionkey", "encryptionkeyid", "env", + "geolocation", "gpgkey", + "ipaddress", "jti", "jwt", "licensekey", @@ -62,9 +66,6 @@ "pin", "pincode", "pkcs8", - "plateno", - "platenum", - "platenumber", "privatekey", "publickey", "pwd", @@ -98,6 +99,7 @@ "xcsrftoken", "xforwardedfor", "xrealip", + "xsrf", "xsrftoken", } ) diff --git a/tests/debugging/exception/test_auto_instrument.py b/tests/debugging/exception/test_auto_instrument.py index 53cee4b88d8..8fc92bffa5f 100644 --- a/tests/debugging/exception/test_auto_instrument.py +++ b/tests/debugging/exception/test_auto_instrument.py @@ -109,7 +109,8 @@ def c(foo=42): with exception_debugging() as d: rate_limiter = RateLimiter( - limit_rate=1, # one trace per second + limit_rate=0.1, # one trace per second + tau=10, raise_on_exceed=False, ) with with_rate_limiter(rate_limiter): @@ -155,7 +156,7 @@ def c(foo=42): exc_ids = set(span.get_tag("_dd.debug.error.exception_id") for span in self.spans) assert len(exc_ids) == number_of_exc_ids - # invoke again (should be in less then 1 sec) + # invoke again (should be in less than 1 sec) with with_rate_limiter(rate_limiter): with pytest.raises(KeyError): c() From d8c21b05de4a7e090b24482497334cd9c0669461 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Tue, 21 May 2024 15:05:08 +0200 Subject: [PATCH 082/104] chore: exclude scripts folder in build package step (#9324) ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [ ] Title is accurate - [ ] All changes are related to the pull request's stated goal - [ ] Description motivates each change - [ ] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [ ] Testing strategy adequately addresses listed risks - [ ] Change is maintainable (easy to change, telemetry, documentation) - [ ] Release note makes sense to a user of the library - [ ] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [ ] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 892325babb3..6daa456e5de 100644 --- a/setup.py +++ b/setup.py @@ -468,7 +468,7 @@ def get_exts_for(name): setup( name="ddtrace", - packages=find_packages(exclude=["tests*", "benchmarks*"]), + packages=find_packages(exclude=["tests*", "benchmarks*", "scripts*"]), package_data={ "ddtrace": ["py.typed"], "ddtrace.appsec": ["rules.json"], From e605bd31f3ceb65a3417f7fcf982f9406477264d Mon Sep 17 00:00:00 2001 From: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> Date: Tue, 21 May 2024 18:58:42 +0200 Subject: [PATCH 083/104] chore(asm): use unpatched open for asm (#9326) To improve performances and avoid unwanted interactions with exploit prevention, this PR ensures that we used unpatched open function for loading rules or blocking content for asm. - add `_unpatched` module to keep track of unpatched references - move threading to _unpatched - ensure we use the unpatched version of open for blocking content and waf rule files ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Alberto Vara Co-authored-by: Brett Langdon --- .github/CODEOWNERS | 1 + ddtrace/__init__.py | 9 ++------- ddtrace/appsec/_iast/_overhead_control_engine.py | 2 +- ddtrace/appsec/_processor.py | 1 + ddtrace/internal/_unpatched.py | 10 ++++++++++ ddtrace/internal/utils/http.py | 1 + ddtrace/profiling/_threading.pyx | 2 +- ddtrace/profiling/collector/stack.pyx | 2 +- tests/.suitespec.json | 1 + 9 files changed, 19 insertions(+), 10 deletions(-) create mode 100644 ddtrace/internal/_unpatched.py diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3cc23a14907..9183c3fb92d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -113,4 +113,5 @@ mypy.ini @DataDog/python-guild @DataDog/apm-core-pyt .github/ISSUE_TEMPLATE.md @DataDog/python-guild @DataDog/apm-core-python .github/CODEOWNERS @DataDog/python-guild @DataDog/apm-core-python .github/workflows/system-tests.yml @DataDog/python-guild @DataDog/apm-core-python +ddtrace/internal/_unpatched.py @DataDog/python-guild ddtrace/internal/compat.py @DataDog/python-guild @DataDog/apm-core-python diff --git a/ddtrace/__init__.py b/ddtrace/__init__.py index 3d900b587c7..ddcbe55807d 100644 --- a/ddtrace/__init__.py +++ b/ddtrace/__init__.py @@ -7,13 +7,8 @@ ModuleWatchdog.install() -# Acquire a reference to the threading module. Some parts of the library (e.g. -# the profiler) might be enabled programmatically and therefore might end up -# getting a reference to the tracee's threading module. By storing a reference -# to the threading module used by ddtrace here, we make it easy for those parts -# to get a reference to the right threading module. -import threading as _threading - +# Ensure we capture references to unpatched modules as early as possible +import ddtrace.internal._unpatched # noqa from ._logger import configure_ddtrace_logger diff --git a/ddtrace/appsec/_iast/_overhead_control_engine.py b/ddtrace/appsec/_iast/_overhead_control_engine.py index 2889455749a..d3e9503047b 100644 --- a/ddtrace/appsec/_iast/_overhead_control_engine.py +++ b/ddtrace/appsec/_iast/_overhead_control_engine.py @@ -9,8 +9,8 @@ from typing import Tuple from typing import Type -from ddtrace import _threading as threading from ddtrace._trace.span import Span +from ddtrace.internal._unpatched import _threading as threading from ddtrace.internal.logger import get_logger from ddtrace.sampler import RateSampler diff --git a/ddtrace/appsec/_processor.py b/ddtrace/appsec/_processor.py index 34aecadb612..3af6f18547d 100644 --- a/ddtrace/appsec/_processor.py +++ b/ddtrace/appsec/_processor.py @@ -38,6 +38,7 @@ from ddtrace.constants import RUNTIME_FAMILY from ddtrace.ext import SpanTypes from ddtrace.internal import core +from ddtrace.internal._unpatched import unpatched_open as open # noqa: A001 from ddtrace.internal.logger import get_logger from ddtrace.internal.rate_limiter import RateLimiter from ddtrace.settings.asm import config as asm_config diff --git a/ddtrace/internal/_unpatched.py b/ddtrace/internal/_unpatched.py new file mode 100644 index 00000000000..98452b62b2e --- /dev/null +++ b/ddtrace/internal/_unpatched.py @@ -0,0 +1,10 @@ +# Acquire a reference to the open function from the builtins module. This is +# necessary to ensure that the open function can be used unpatched when required. +from builtins import open as unpatched_open # noqa + +# Acquire a reference to the threading module. Some parts of the library (e.g. +# the profiler) might be enabled programmatically and therefore might end up +# getting a reference to the tracee's threading module. By storing a reference +# to the threading module used by ddtrace here, we make it easy for those parts +# to get a reference to the right threading module. +import threading as _threading # noqa diff --git a/ddtrace/internal/utils/http.py b/ddtrace/internal/utils/http.py index d77134ad9f7..ba93dda0d71 100644 --- a/ddtrace/internal/utils/http.py +++ b/ddtrace/internal/utils/http.py @@ -17,6 +17,7 @@ from ddtrace.constants import USER_ID_KEY from ddtrace.internal import compat +from ddtrace.internal._unpatched import unpatched_open as open # noqa: A001 from ddtrace.internal.compat import parse from ddtrace.internal.constants import BLOCKED_RESPONSE_HTML from ddtrace.internal.constants import BLOCKED_RESPONSE_JSON diff --git a/ddtrace/profiling/_threading.pyx b/ddtrace/profiling/_threading.pyx index 488ba87a1ce..93a121a3ce2 100644 --- a/ddtrace/profiling/_threading.pyx +++ b/ddtrace/profiling/_threading.pyx @@ -7,7 +7,7 @@ import weakref import attr from six.moves import _thread -from ddtrace import _threading as ddtrace_threading +from ddtrace.internal._unpatched import _threading as ddtrace_threading from cpython cimport PyLong_FromLong diff --git a/ddtrace/profiling/collector/stack.pyx b/ddtrace/profiling/collector/stack.pyx index 7d99b2d33a4..b49720d55a4 100644 --- a/ddtrace/profiling/collector/stack.pyx +++ b/ddtrace/profiling/collector/stack.pyx @@ -8,7 +8,7 @@ import typing import attr import six -from ddtrace import _threading as ddtrace_threading +from ddtrace.internal._unpatched import _threading as ddtrace_threading from ddtrace._trace import context from ddtrace._trace import span as ddspan from ddtrace.internal import compat diff --git a/tests/.suitespec.json b/tests/.suitespec.json index a01771321a8..6ceccc20723 100644 --- a/tests/.suitespec.json +++ b/tests/.suitespec.json @@ -38,6 +38,7 @@ "ddtrace/internal/_rand.pyi", "ddtrace/internal/_rand.pyx", "ddtrace/internal/_stdint.h", + "ddtrace/internal/_unpatched.py", "ddtrace/internal/agent.py", "ddtrace/internal/assembly.py", "ddtrace/internal/atexit.py", From 3c618abfc27b4a70986c1db41d82d6aa0a0268ef Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Tue, 21 May 2024 18:38:01 +0100 Subject: [PATCH 084/104] chore: retrieve locals from function probes (#9327) We make use of the new bytecode context wrapping to gain access to local variables in function probes. ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [ ] Title is accurate - [ ] All changes are related to the pull request's stated goal - [ ] Description motivates each change - [ ] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [ ] Testing strategy adequately addresses listed risks - [ ] Change is maintainable (easy to change, telemetry, documentation) - [ ] Release note makes sense to a user of the library - [ ] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [ ] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/debugging/_debugger.py | 274 +++++++++++++------------ ddtrace/debugging/_function/store.py | 16 +- ddtrace/debugging/_signal/snapshot.py | 2 +- tests/debugging/function/test_store.py | 75 +++---- tests/debugging/test_debugger.py | 56 ++++- 5 files changed, 227 insertions(+), 196 deletions(-) diff --git a/ddtrace/debugging/_debugger.py b/ddtrace/debugging/_debugger.py index f86d73ba5fb..824c5c14ed0 100644 --- a/ddtrace/debugging/_debugger.py +++ b/ddtrace/debugging/_debugger.py @@ -6,23 +6,22 @@ from pathlib import Path import sys import threading -from types import CoroutineType from types import FunctionType from types import ModuleType -from typing import Any +from types import TracebackType from typing import Deque from typing import Dict from typing import Iterable from typing import List from typing import Optional from typing import Set -from typing import Tuple +from typing import Type +from typing import TypeVar from typing import cast import ddtrace from ddtrace import config as ddconfig from ddtrace._trace.tracer import Tracer -from ddtrace.debugging._async import dd_coroutine_wrapper from ddtrace.debugging._config import di_config from ddtrace.debugging._config import ed_config from ddtrace.debugging._encoding import LogSignalJsonEncoder @@ -49,6 +48,7 @@ from ddtrace.debugging._probe.remoteconfig import ProbePollerEventType from ddtrace.debugging._probe.remoteconfig import ProbeRCAdapter from ddtrace.debugging._probe.status import ProbeStatusLogger +from ddtrace.debugging._safety import get_args from ddtrace.debugging._signal.collector import SignalCollector from ddtrace.debugging._signal.collector import SignalContext from ddtrace.debugging._signal.metric_sample import MetricSample @@ -71,9 +71,8 @@ from ddtrace.internal.rate_limiter import BudgetRateLimiterWithJitter as RateLimiter from ddtrace.internal.rate_limiter import RateLimitExceeded from ddtrace.internal.remoteconfig.worker import remoteconfig_poller -from ddtrace.internal.safety import _isinstance from ddtrace.internal.service import Service -from ddtrace.internal.wrapping import Wrapper +from ddtrace.internal.wrapping.context import WrappingContext log = get_logger(__name__) @@ -81,6 +80,8 @@ _probe_metrics = Metrics(namespace="dynamic.instrumentation.metric") _probe_metrics.enable() +T = TypeVar("T") + class DebuggerError(Exception): """Generic debugger error.""" @@ -149,6 +150,124 @@ def on_run_module(cls, module: ModuleType) -> None: cls._instance.after_import(module) +class DebuggerWrappingContext(WrappingContext): + __priority__ = 99 # Execute after all other contexts + + def __init__( + self, f, collector: SignalCollector, registry: ProbeRegistry, tracer: Tracer, probe_meter: Metrics.Meter + ) -> None: + super().__init__(f) + + self._collector = collector + self._probe_registry = registry + self._tracer = tracer + self._probe_meter = probe_meter + + self.probes: Dict[str, Probe] = {} + + def add_probe(self, probe: Probe) -> None: + self.probes[probe.probe_id] = probe + + def remove_probe(self, probe: Probe) -> None: + del self.probes[probe.probe_id] + + def has_probes(self) -> bool: + return bool(self.probes) + + def _close_contexts(self, retval=None, exc_info=(None, None, None)) -> None: + end_time = compat.monotonic_ns() + contexts = self.get("contexts") + while contexts: + # Open probe signal contexts are ordered, with those that have + # created new tracing context first. We need to finalise them in + # reverse order, so we pop them from the end of the queue (LIFO). + context = contexts.pop() + context.exit(retval, exc_info, end_time - self.get("start_time")) + signal = context.signal + if signal.state is SignalState.DONE: + self._probe_registry.set_emitting(signal.probe) + + def __enter__(self) -> "DebuggerWrappingContext": + super().__enter__() + + frame = self.__frame__ + assert frame is not None # nosec + + args = list(get_args(frame)) + thread = threading.current_thread() + + signal: Optional[Signal] = None + + # Group probes on the basis of whether they create new context. + context_creators: List[Probe] = [] + context_consumers: List[Probe] = [] + for p in self.probes.values(): + (context_creators if p.__context_creator__ else context_consumers).append(p) + + contexts: Deque[SignalContext] = deque() + + # Trigger the context creators first, so that the new context can be + # consumed by the consumers. + for probe in chain(context_creators, context_consumers): + # Because new context might be created, we need to recompute it + # for each probe. + trace_context = self._tracer.current_trace_context() + + if isinstance(probe, MetricFunctionProbe): + signal = MetricSample( + probe=probe, + frame=frame, + thread=thread, + args=args, + trace_context=trace_context, + meter=self._probe_meter, + ) + elif isinstance(probe, LogFunctionProbe): + signal = Snapshot( + probe=probe, + frame=frame, + thread=thread, + args=args, + trace_context=trace_context, + ) + elif isinstance(probe, SpanFunctionProbe): + signal = DynamicSpan( + probe=probe, + frame=frame, + thread=thread, + args=args, + trace_context=trace_context, + ) + elif isinstance(probe, SpanDecorationFunctionProbe): + signal = SpanDecoration( + probe=probe, + frame=frame, + thread=thread, + args=args, + ) + else: + log.error("Unsupported probe type: %s", type(probe)) + continue + + contexts.append(self._collector.attach(signal)) + + # Save state on the wrapping context + self.set("start_time", compat.monotonic_ns()) + self.set("contexts", contexts) + + return self + + def __return__(self, value: T) -> T: + self._close_contexts(retval=value) + return super().__return__(value) + + def __exit__( + self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType] + ) -> None: + self._close_contexts(exc_info=(exc_type, exc_val, exc_tb)) + super().__exit__(exc_type, exc_val, exc_tb) + + class Debugger(Service): _instance: Optional["Debugger"] = None _probe_meter = _probe_metrics.get_meter("probe") @@ -329,114 +448,6 @@ def _dd_debugger_hook(self, probe: Probe) -> None: except Exception: log.error("Failed to execute probe hook", exc_info=True) - def _dd_debugger_wrapper(self, wrappers: Dict[str, FunctionProbe]) -> Wrapper: - """Debugger wrapper. - - This gets called with a reference to the wrapped function and the probe, - together with the arguments to pass. We only check - whether the probe is active and the debugger is enabled. If so, we - capture all the relevant debugging context. - """ - - def _(wrapped: FunctionType, args: Tuple[Any], kwargs: Dict[str, Any]) -> Any: - if not wrappers: - return wrapped(*args, **kwargs) - - argnames = wrapped.__code__.co_varnames - actual_frame = sys._getframe(1) - allargs = list(chain(zip(argnames, args), kwargs.items())) - thread = threading.current_thread() - - open_contexts: Deque[SignalContext] = deque() - signal: Optional[Signal] = None - - # Group probes on the basis of whether they create new context. - context_creators: List[Probe] = [] - context_consumers: List[Probe] = [] - for p in wrappers.values(): - (context_creators if p.__context_creator__ else context_consumers).append(p) - - # Trigger the context creators first, so that the new context can be - # consumed by the consumers. - for probe in chain(context_creators, context_consumers): - # Because new context might be created, we need to recompute it - # for each probe. - trace_context = self._tracer.current_trace_context() - - if isinstance(probe, MetricFunctionProbe): - signal = MetricSample( - probe=probe, - frame=actual_frame, - thread=thread, - args=allargs, - trace_context=trace_context, - meter=self._probe_meter, - ) - elif isinstance(probe, LogFunctionProbe): - signal = Snapshot( - probe=probe, - frame=actual_frame, - thread=thread, - args=allargs, - trace_context=trace_context, - ) - elif isinstance(probe, SpanFunctionProbe): - signal = DynamicSpan( - probe=probe, - frame=actual_frame, - thread=thread, - args=allargs, - trace_context=trace_context, - ) - elif isinstance(probe, SpanDecorationFunctionProbe): - signal = SpanDecoration( - probe=probe, - frame=actual_frame, - thread=thread, - args=allargs, - ) - else: - log.error("Unsupported probe type: %s", type(probe)) - continue - - # Open probe signal contexts are ordered, with those that have - # created new tracing context first. We need to finalise them in - # reverse order, so we append them to the beginning. - open_contexts.appendleft(self._collector.attach(signal)) - - if not open_contexts: - return wrapped(*args, **kwargs) - - start_time = compat.monotonic_ns() - try: - retval = wrapped(*args, **kwargs) - end_time = compat.monotonic_ns() - exc_info = (None, None, None) - except Exception: - end_time = compat.monotonic_ns() - retval = None - exc_info = sys.exc_info() # type: ignore[assignment] - else: - # DEV: We do not unwind generators here as they might result in - # tight loops. We return the result as a generator object - # instead. - if _isinstance(retval, CoroutineType): - return dd_coroutine_wrapper(retval, open_contexts) - - for context in open_contexts: - context.exit(retval, exc_info, end_time - start_time) - signal = context.signal - if signal.state is SignalState.DONE: - self._probe_registry.set_emitting(signal.probe) - - exc = exc_info[1] - if exc is not None: - raise exc - - return retval - - return _ - def _probe_injection_hook(self, module: ModuleType) -> None: # This hook is invoked by the ModuleWatchdog or the post run module hook # to inject probes. @@ -578,7 +589,7 @@ def _probe_wrapping_hook(self, module: ModuleType) -> None: try: assert probe.module is not None and probe.func_qname is not None # nosec - function = FunctionDiscovery.from_module(module).by_name(probe.func_qname) + function = cast(FunctionType, FunctionDiscovery.from_module(module).by_name(probe.func_qname)) except ValueError: message = ( f"Cannot install probe {probe.probe_id}: no function '{probe.func_qname}' in module {probe.module}" @@ -588,11 +599,8 @@ def _probe_wrapping_hook(self, module: ModuleType) -> None: log.error(message) continue - if hasattr(function, "__dd_wrappers__"): - # TODO: Check if this can be made into a set instead - wrapper = cast(FullyNamedWrappedFunction, function) - assert wrapper.__dd_wrappers__, "Function has debugger wrappers" # nosec - wrapper.__dd_wrappers__[probe.probe_id] = probe + if DebuggerWrappingContext.is_wrapped(function): + context = cast(DebuggerWrappingContext, DebuggerWrappingContext.extract(function)) log.debug( "[%s][P: %s] Function probe %r added to already wrapped %r", os.getpid(), @@ -601,8 +609,14 @@ def _probe_wrapping_hook(self, module: ModuleType) -> None: function, ) else: - wrappers = cast(FullyNamedWrappedFunction, function).__dd_wrappers__ = {probe.probe_id: probe} - self._function_store.wrap(cast(FunctionType, function), self._dd_debugger_wrapper(wrappers)) + context = DebuggerWrappingContext( + function, + collector=self._collector, + registry=self._probe_registry, + tracer=self._tracer, + probe_meter=self._probe_meter, + ) + self._function_store.wrap(cast(FunctionType, function), context) log.debug( "[%s][P: %s] Function probe %r wrapped around %r", os.getpid(), @@ -610,6 +624,8 @@ def _probe_wrapping_hook(self, module: ModuleType) -> None: probe.probe_id, function, ) + + context.add_probe(probe) self._probe_registry.set_installed(probe) def _wrap_functions(self, probes: List[FunctionProbe]) -> None: @@ -646,14 +662,12 @@ def _unwrap_functions(self, probes: List[FunctionProbe]) -> None: # The module is still loaded, so we can try to unwrap the function touched_modules.add(probe.module) assert probe.func_qname is not None # nosec - function = FunctionDiscovery.from_module(module).by_name(probe.func_qname) - if hasattr(function, "__dd_wrappers__"): - wrapper = cast(FullyNamedWrappedFunction, function) - assert wrapper.__dd_wrappers__, "Function has debugger wrappers" # nosec - del wrapper.__dd_wrappers__[probe.probe_id] - if not wrapper.__dd_wrappers__: - del wrapper.__dd_wrappers__ - self._function_store.unwrap(wrapper) + function = cast(FunctionType, FunctionDiscovery.from_module(module).by_name(probe.func_qname)) + if DebuggerWrappingContext.is_wrapped(function): + context = cast(DebuggerWrappingContext, DebuggerWrappingContext.extract(function)) + context.remove_probe(probe) + if not context.has_probes(): + self._function_store.unwrap(cast(FullyNamedWrappedFunction, function)) log.debug("Unwrapped %r", registered_probe) else: log.error("Attempted to unwrap %r, but no wrapper found", registered_probe) diff --git a/ddtrace/debugging/_function/store.py b/ddtrace/debugging/_function/store.py index bdc8988bb0a..9a17aae3b91 100644 --- a/ddtrace/debugging/_function/store.py +++ b/ddtrace/debugging/_function/store.py @@ -14,9 +14,7 @@ from ddtrace.internal.injection import eject_hooks from ddtrace.internal.injection import inject_hooks from ddtrace.internal.wrapping import WrappedFunction -from ddtrace.internal.wrapping import Wrapper -from ddtrace.internal.wrapping import unwrap -from ddtrace.internal.wrapping import wrap +from ddtrace.internal.wrapping.context import WrappingContext WrapperType = Callable[[FunctionType, Any, Any, Any], Any] @@ -41,8 +39,8 @@ class FunctionStore(object): def __init__(self, extra_attrs: Optional[List[str]] = None) -> None: self._code_map: Dict[FunctionType, CodeType] = {} - self._wrapper_map: Dict[FunctionType, Wrapper] = {} - self._extra_attrs = ["__dd_wrapped__"] + self._wrapper_map: Dict[FunctionType, WrappingContext] = {} + self._extra_attrs = ["__dd_context_wrapped__"] if extra_attrs: self._extra_attrs.extend(extra_attrs) @@ -90,15 +88,15 @@ def eject_hook(self, function: FunctionType, hook: HookType, line: int, arg: Any """Eject a hook from a function.""" return not not self.eject_hooks(function, [(hook, line, arg)]) - def wrap(self, function: FunctionType, wrapper: Wrapper) -> None: + def wrap(self, function: FunctionType, wrapping_context: WrappingContext) -> None: """Wrap a function with a hook.""" self._store(function) - self._wrapper_map[function] = wrapper - wrap(function, wrapper) + self._wrapper_map[function] = wrapping_context + wrapping_context.wrap() def unwrap(self, function: FullyNamedWrappedFunction) -> None: """Unwrap a hook around a wrapped function.""" - unwrap(function, self._wrapper_map.pop(cast(FunctionType, function))) + self._wrapper_map.pop(cast(FunctionType, function)).unwrap() def restore_all(self) -> None: """Restore all the patched functions to their original form.""" diff --git a/ddtrace/debugging/_signal/snapshot.py b/ddtrace/debugging/_signal/snapshot.py index d9f5ec99cf3..2be42dcd138 100644 --- a/ddtrace/debugging/_signal/snapshot.py +++ b/ddtrace/debugging/_signal/snapshot.py @@ -158,7 +158,7 @@ def exit(self, retval, exc_info, duration): elif self.state not in {SignalState.NONE, SignalState.DONE}: return - _locals = [] + _locals = list(_safety.get_locals(self.frame)) _, exc, _ = exc_info if exc is None: _locals.append(("@return", retval)) diff --git a/tests/debugging/function/test_store.py b/tests/debugging/function/test_store.py index 3bcbe3e2d9f..e9d1901dc52 100644 --- a/tests/debugging/function/test_store.py +++ b/tests/debugging/function/test_store.py @@ -1,5 +1,3 @@ -import sys - import mock from mock.mock import call @@ -8,27 +6,28 @@ from ddtrace.debugging._function.store import FunctionStore from ddtrace.internal.module import origin from ddtrace.internal.utils.inspection import linenos +from ddtrace.internal.wrapping.context import WrappingContext import tests.submod.stuff as stuff -def gen_wrapper(*arg): - def _wrapper(wrapped, args, kwargs): - # take a snapshot of args and kwargs before they are modified by the - # wrapped function - try: - result = wrapped(*args, **kwargs) - # capture the return value - mock, v = arg - mock(v) - return result - except Exception: - # capture the exception - raise - finally: - # finalise and push the snapshot - pass +class MockWrappingContext(WrappingContext): + def __init__(self, f, mock, arg): + super().__init__(f) + + self.mock = mock + self.arg = arg + + def __exit__(self, *exc): + pass - return _wrapper + def __return__(self, value): + self.mock(self.arg) + return value + + +class MockProbe: + def __init__(self, probe_id): + self.probe_id = probe_id def test_function_inject(): @@ -52,7 +51,7 @@ def test_function_wrap(): assert function is stuff.modulestuff arg = mock.Mock() - store.wrap(function, gen_wrapper(arg, 42)) + store.wrap(function, MockWrappingContext(function, arg, 42)) stuff.modulestuff(None) @@ -78,7 +77,7 @@ def test_function_inject_wrap(): # Wrapping arg = mock.Mock() - store.wrap(stuff.modulestuff, gen_wrapper(arg, 42)) + store.wrap(stuff.modulestuff, MockWrappingContext(stuff.modulestuff, arg, 42)) stuff.modulestuff(None) arg.assert_called_once_with(42) @@ -98,7 +97,7 @@ def test_function_wrap_inject(): # Wrapping arg = mock.Mock() - store.wrap(function, gen_wrapper(arg, 42)) + store.wrap(function, MockWrappingContext(function, arg, 42)) stuff.modulestuff(None) arg.assert_called_once_with(42) @@ -124,7 +123,7 @@ def test_function_wrap_property(): assert function is stuff.Stuff.propertystuff.fget arg = mock.Mock() - store.wrap(function, gen_wrapper(arg, 42)) + store.wrap(function, MockWrappingContext(function, arg, 42)) s = stuff.Stuff() s.propertystuff @@ -146,7 +145,7 @@ def test_function_wrap_decorated(): assert function is undecorated(stuff.Stuff.doublydecoratedstuff, "doublydecoratedstuff", origin(stuff)) arg = mock.Mock() - store.wrap(function, gen_wrapper(arg, 42)) + store.wrap(function, MockWrappingContext(function, arg, 42)) s = stuff.Stuff() s.doublydecoratedstuff() @@ -162,13 +161,10 @@ def test_function_unwrap(): with FunctionStore() as store: function = FunctionDiscovery.from_module(stuff).by_name(stuff.modulestuff.__name__) assert function is stuff.modulestuff - code = function.__code__ - store.wrap(function, gen_wrapper(None, 42)) - assert code is not stuff.modulestuff.__code__ + store.wrap(function, MockWrappingContext(function, None, 42)) store.unwrap(stuff.modulestuff) - assert code is stuff.modulestuff.__code__ def test_function_inject_wrap_commutativity(): @@ -184,8 +180,7 @@ def test_function_inject_wrap_commutativity(): # Wrapping function = FunctionDiscovery.from_module(stuff).by_name(stuff.modulestuff.__name__) assert function.__code__ is stuff.modulestuff.__code__ - store.wrap(function, gen_wrapper(None, 42)) - assert code is not stuff.modulestuff.__code__ + store.wrap(function, MockWrappingContext(function, None, 42)) # Ejection assert stuff.modulestuff.__code__ is not code @@ -194,33 +189,23 @@ def test_function_inject_wrap_commutativity(): # Unwrapping store.unwrap(stuff.modulestuff) - assert stuff.modulestuff.__code__ is not code - if sys.version_info < (3, 12): - assert stuff.modulestuff.__code__ == code - def test_function_wrap_inject_commutativity(): with FunctionStore() as store: # Wrapping function = FunctionDiscovery.from_module(stuff).by_name(stuff.modulestuff.__name__) assert function is stuff.modulestuff - code = function.__code__ - store.wrap(function, gen_wrapper(None, 42)) - assert code is not stuff.modulestuff.__code__ + lo = min(linenos(function)) + store.wrap(function, MockWrappingContext(function, None, 42)) # Injection - lo = min(linenos(stuff.modulestuff.__dd_wrapped__)) function = FunctionDiscovery.from_module(stuff).at_line(lo)[0] hook = mock.Mock()() - store.inject_hook(function, hook, lo, 42) + probe = MockProbe(42) + store.inject_hook(function, hook, lo, probe) # Unwrapping store.unwrap(stuff.modulestuff) # Ejection - assert stuff.modulestuff.__code__ is not code - store.eject_hook(stuff.modulestuff, hook, lo, 42) - - assert stuff.modulestuff.__code__ is not code - if sys.version_info < (3, 12): - assert stuff.modulestuff.__code__ == code + store.eject_hook(stuff.modulestuff, hook, lo, probe) diff --git a/tests/debugging/test_debugger.py b/tests/debugging/test_debugger.py index 22dbe1060ea..477fe202749 100644 --- a/tests/debugging/test_debugger.py +++ b/tests/debugging/test_debugger.py @@ -9,6 +9,7 @@ import ddtrace from ddtrace.constants import ORIGIN_KEY +from ddtrace.debugging._debugger import DebuggerWrappingContext from ddtrace.debugging._probe.model import DDExpression from ddtrace.debugging._probe.model import MetricProbeKind from ddtrace.debugging._probe.model import ProbeEvaluateTimingForMethod @@ -211,7 +212,7 @@ def test_debugger_function_probe_on_function_with_exception(): func_qname="throwexcstuff", condition=None, ), - lambda: stuff.throwexcstuff(), + stuff.throwexcstuff, ) (snapshot,) = snapshots @@ -497,7 +498,8 @@ def test_debugger_multiple_function_probes_on_same_function(): with debugger() as d: d.add_probes(*probes) - assert Stuff.instancestuff.__dd_wrappers__ == {probe.probe_id: probe for probe in probes} + wrapping_context = DebuggerWrappingContext.extract(Stuff.instancestuff) + assert wrapping_context.probes == {probe.probe_id: probe for probe in probes} Stuff().instancestuff(42) d.collector.wait( @@ -511,7 +513,7 @@ def test_debugger_multiple_function_probes_on_same_function(): d.remove_probes(probes[1]) - assert "probe-instance-method-1" not in Stuff.instancestuff.__dd_wrappers__ + assert "probe-instance-method-1" not in wrapping_context.probes Stuff().instancestuff(42) @@ -603,10 +605,6 @@ def test_debugger_wrapped_function_on_function_probe(stuff): d.add_probes(*probes) wrapt.wrap_function_wrapper(stuff, "Stuff.instancestuff", wrapper) - assert stuff.Stuff.instancestuff.__code__ is stuff.Stuff.instancestuff.__wrapped__.__code__ - assert stuff.Stuff.instancestuff.__code__ is not code - assert stuff.Stuff.instancestuff.__wrapped__.__dd_wrapped__.__code__ is code - assert stuff.Stuff.instancestuff is not f stuff.Stuff().instancestuff(42) @@ -1165,20 +1163,26 @@ def test_debugger_redacted_identifiers(): " pii_dict['jwt']=", {"dsl": "pii_dict['jwt']", "json": {"index": [{"ref": "pii_dict"}, "jwt"]}}, ), - ) + ), + create_snapshot_function_probe( + probe_id="function-probe", + module="tests.submod.stuff", + func_qname="sensitive_stuff", + evaluate_at=ProbeEvaluateTimingForMethod.EXIT, + ), ) stuff.sensitive_stuff("top secret") - ((msg,),) = d.uploader.wait_for_payloads() + ((msg_line, msg_func),) = d.uploader.wait_for_payloads() assert ( - msg["message"] == f"token={REDACTED} answer=42 " + msg_line["message"] == f"token={REDACTED} answer=42 " f"pii_dict={{'jwt': '{REDACTED}', 'password': '{REDACTED}', 'username': 'admin'}} " f"pii_dict['jwt']={REDACTED}" ) - assert msg["debugger.snapshot"]["captures"]["lines"]["169"] == { + assert msg_line["debugger.snapshot"]["captures"]["lines"]["169"] == { "arguments": {"pwd": redacted_value(str())}, "locals": { "token": redacted_value(str()), @@ -1201,6 +1205,36 @@ def test_debugger_redacted_identifiers(): "throwable": None, } + assert msg_func["debugger.snapshot"]["captures"] == { + "entry": {"arguments": {}, "locals": {}, "staticFields": {}, "throwable": None}, + "return": { + "arguments": {"pwd": {"type": "str", "notCapturedReason": "redactedIdent"}}, + "locals": { + "token": {"type": "str", "notCapturedReason": "redactedIdent"}, + "answer": {"type": "int", "value": "42"}, + "data": { + "type": "SensitiveData", + "fields": {"password": {"type": "str", "notCapturedReason": "redactedIdent"}}, + }, + "pii_dict": { + "type": "dict", + "entries": [ + [{"type": "str", "value": "'jwt'"}, {"type": "str", "notCapturedReason": "redactedIdent"}], + [ + {"type": "str", "value": "'password'"}, + {"type": "str", "notCapturedReason": "redactedIdent"}, + ], + [{"type": "str", "value": "'username'"}, {"type": "str", "value": "'admin'"}], + ], + "size": 3, + }, + "@return": {"type": "str", "value": "'top secret'"}, # TODO: Ouch! + }, + "staticFields": {}, + "throwable": None, + }, + } + def test_debugger_exception_conditional_function_probe(): """ From 4b40fce99108a46e05e2695a86d9d609564d4544 Mon Sep 17 00:00:00 2001 From: erikayasuda <153395705+erikayasuda@users.noreply.github.com> Date: Tue, 21 May 2024 14:01:55 -0400 Subject: [PATCH 085/104] fix(tracer): ensure all metadata and configs are checked for flare + correct config file (#9322) ## Overview This PR fixes two bugs with the tracer flare. First, the RC poller behavior for the tracer flare. Prior to this PR, it would assume that the first product when polling the RC backend would be the target config/metadata we need. However, this is not a safe assumption as multiple products could be updated and polled at once. This, in addition to the fact that currently the tracer flare shares the `AGENT_CONFIG` and `AGENT_TASK` product with the **agent** flare, means that we need to check all products to ensure we don't accidentally skip a tracer flare product. Second, we were passing in the RC config, not the actual ddtrace config when generating the config file for the flare. This is fixed, and the correct config is being generated now. ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/internal/flare/_subscribers.py | 21 ++++++++++++--------- ddtrace/internal/flare/flare.py | 14 ++++++++------ ddtrace/internal/flare/handler.py | 12 +++++++----- ddtrace/settings/config.py | 2 +- tests/internal/test_tracer_flare.py | 16 +++++++++------- 5 files changed, 37 insertions(+), 28 deletions(-) diff --git a/ddtrace/internal/flare/_subscribers.py b/ddtrace/internal/flare/_subscribers.py index 17703f6de01..29ea0bea658 100644 --- a/ddtrace/internal/flare/_subscribers.py +++ b/ddtrace/internal/flare/_subscribers.py @@ -1,9 +1,10 @@ from datetime import datetime -import os from typing import Callable # noqa:F401 from typing import Optional # noqa:F401 from ddtrace.internal.flare.flare import Flare +from ddtrace.internal.flare.handler import _generate_tracer_flare +from ddtrace.internal.flare.handler import _prepare_tracer_flare from ddtrace.internal.logger import get_logger from ddtrace.internal.remoteconfig._connectors import PublisherSubscriberConnector # noqa:F401 from ddtrace.internal.remoteconfig._subscribers import RemoteConfigSubscriber @@ -51,6 +52,10 @@ def _get_data_from_connector_and_exec(self): if not metadata: log.debug("No metadata received from data connector") return + configs = data.get("config") + if not configs: + log.debug("No config items received from data connector") + return for md in metadata: product_type = md.get("product_name") @@ -61,18 +66,16 @@ def _get_data_from_connector_and_exec(self): "There is already a tracer flare job started at %s. Skipping new request.", str(self.current_request_start), ) - return - self.current_request_start = datetime.now() + continue + if _prepare_tracer_flare(self.flare, configs): + self.current_request_start = datetime.now() elif product_type == "AGENT_TASK": # Possible edge case where we don't have an existing flare request # In this case we won't have anything to send, so we log and do nothing if self.current_request_start is None: log.warning("There is no tracer flare job to complete. Skipping new request.") - return - self.current_request_start = None + continue + if _generate_tracer_flare(self.flare, configs): + self.current_request_start = None else: log.debug("Received unexpected product type for tracer flare: {}", product_type) - return - log.debug("[PID %d] %s _exec_callback: %s", os.getpid(), self, str(data)[:50]) - self._callback(self.flare, data) - return diff --git a/ddtrace/internal/flare/flare.py b/ddtrace/internal/flare/flare.py index 8cde2f5a202..45a555c1a86 100644 --- a/ddtrace/internal/flare/flare.py +++ b/ddtrace/internal/flare/flare.py @@ -43,6 +43,7 @@ class Flare: def __init__( self, trace_agent_url: str, + ddconfig: dict, api_key: Optional[str] = None, timeout_sec: int = DEFAULT_TIMEOUT_SECONDS, flare_dir: str = TRACER_FLARE_DIRECTORY, @@ -53,8 +54,9 @@ def __init__( self.file_handler: Optional[RotatingFileHandler] = None self.url: str = trace_agent_url self._api_key: Optional[str] = api_key + self.ddconfig = ddconfig - def prepare(self, config: dict, log_level: str): + def prepare(self, log_level: str): """ Update configurations to start sending tracer logs to a file to be sent in a flare later. @@ -88,7 +90,7 @@ def prepare(self, config: dict, log_level: str): ) # Create and add config file - self._generate_config_file(config, pid) + self._generate_config_file(pid) def send(self, flare_send_req: FlareSendRequest): """ @@ -128,17 +130,17 @@ def send(self, flare_send_req: FlareSendRequest): # Clean up files regardless of success/failure self.clean_up_files() - def _generate_config_file(self, config: dict, pid: int): + def _generate_config_file(self, pid: int): config_file = self.flare_dir / f"tracer_config_{pid}.json" try: with open(config_file, "w") as f: # Redact API key if present - api_key = config.get("_dd_api_key") + api_key = self.ddconfig.get("_dd_api_key") if api_key: - config["_dd_api_key"] = "*" * (len(api_key) - 4) + api_key[-4:] + self.ddconfig["_dd_api_key"] = "*" * (len(api_key) - 4) + api_key[-4:] tracer_configs = { - "configs": config, + "configs": self.ddconfig, } json.dump( tracer_configs, diff --git a/ddtrace/internal/flare/handler.py b/ddtrace/internal/flare/handler.py index 9cb639ade8b..75ddac35188 100644 --- a/ddtrace/internal/flare/handler.py +++ b/ddtrace/internal/flare/handler.py @@ -51,7 +51,7 @@ def _handle_tracer_flare(flare: Flare, data: dict, cleanup: bool = False): log.warning("Received unexpected tracer flare product type: %s", product_type) -def _prepare_tracer_flare(flare: Flare, configs: List[dict]): +def _prepare_tracer_flare(flare: Flare, configs: List[dict]) -> bool: """ Update configurations to start sending tracer logs to a file to be sent in a flare later. @@ -64,11 +64,12 @@ def _prepare_tracer_flare(flare: Flare, configs: List[dict]): continue flare_log_level = c.get("config", {}).get("log_level").upper() - flare.prepare(c, flare_log_level) - return + flare.prepare(flare_log_level) + return True + return False -def _generate_tracer_flare(flare: Flare, configs: List[Any]): +def _generate_tracer_flare(flare: Flare, configs: List[Any]) -> bool: """ Revert tracer flare configurations back to original state before sending the flare. @@ -87,4 +88,5 @@ def _generate_tracer_flare(flare: Flare, configs: List[Any]): flare.revert_configs() flare.send(flare_request) - return + return True + return False diff --git a/ddtrace/settings/config.py b/ddtrace/settings/config.py index 148eb0aeabd..47fe9dbeef6 100644 --- a/ddtrace/settings/config.py +++ b/ddtrace/settings/config.py @@ -815,7 +815,7 @@ def enable_remote_configuration(self): from ddtrace.internal.remoteconfig.worker import remoteconfig_poller remoteconfig_pubsub = self._remoteconfigPubSub()(self._handle_remoteconfig) - flare = Flare(trace_agent_url=self._trace_agent_url, api_key=self._dd_api_key) + flare = Flare(trace_agent_url=self._trace_agent_url, api_key=self._dd_api_key, ddconfig=self.__dict__) tracerflare_pubsub = _tracerFlarePubSub()(_handle_tracer_flare, flare) remoteconfig_poller.register("APM_TRACING", remoteconfig_pubsub) remoteconfig_poller.register("AGENT_CONFIG", tracerflare_pubsub) diff --git a/tests/internal/test_tracer_flare.py b/tests/internal/test_tracer_flare.py index c6af55a0a95..ef85482197f 100644 --- a/tests/internal/test_tracer_flare.py +++ b/tests/internal/test_tracer_flare.py @@ -33,7 +33,9 @@ class TracerFlareTests(unittest.TestCase): def setUp(self): self.flare_uuid = uuid.uuid4() self.flare_dir = f"{TRACER_FLARE_DIRECTORY}-{self.flare_uuid}" - self.flare = Flare(trace_agent_url=TRACE_AGENT_URL, flare_dir=pathlib.Path(self.flare_dir)) + self.flare = Flare( + trace_agent_url=TRACE_AGENT_URL, flare_dir=pathlib.Path(self.flare_dir), ddconfig={"config": "testconfig"} + ) self.pid = os.getpid() self.flare_file_path = f"{self.flare_dir}/tracer_python_{self.pid}.log" self.config_file_path = f"{self.flare_dir}/tracer_config_{self.pid}.json" @@ -55,7 +57,7 @@ def test_single_process_success(self): """ ddlogger = get_logger("ddtrace") - self.flare.prepare(self.mock_config_dict, "DEBUG") + self.flare.prepare("DEBUG") file_handler = self._get_handler() valid_logger_level = self.flare._get_valid_logger_level(DEBUG_LEVEL_INT) @@ -81,7 +83,7 @@ def test_single_process_partial_failure(self): # Mock the partial failure with mock.patch("json.dump") as mock_json: mock_json.side_effect = Exception("this is an expected error") - self.flare.prepare(self.mock_config_dict, "DEBUG") + self.flare.prepare("DEBUG") file_handler = self._get_handler() assert file_handler is not None @@ -101,7 +103,7 @@ def test_multiple_process_success(self): num_processes = 3 def handle_agent_config(): - self.flare.prepare(self.mock_config_dict, "DEBUG") + self.flare.prepare("DEBUG") def handle_agent_task(): self.flare.send(self.mock_flare_send_request) @@ -134,7 +136,7 @@ def test_multiple_process_partial_failure(self): processes = [] def do_tracer_flare(prep_request, send_request): - self.flare.prepare(self.mock_config_dict, prep_request) + self.flare.prepare(prep_request) # Assert that only one process wrote its file successfully # We check for 2 files because it will generate a log file and a config file assert 2 == len(os.listdir(self.flare_dir)) @@ -157,7 +159,7 @@ def test_no_app_logs(self): file, just the tracer logs """ app_logger = Logger(name="my-app", level=DEBUG_LEVEL_INT) - self.flare.prepare(self.mock_config_dict, "DEBUG") + self.flare.prepare("DEBUG") app_log_line = "this is an app log" app_logger.debug(app_log_line) @@ -195,7 +197,7 @@ def setUp(self): self.tracer_flare_sub = TracerFlareSubscriber( data_connector=PublisherSubscriberConnector(), callback=_handle_tracer_flare, - flare=Flare(trace_agent_url=TRACE_AGENT_URL), + flare=Flare(trace_agent_url=TRACE_AGENT_URL, ddconfig={"config": "testconfig"}), ) def generate_agent_config(self): From 06b8494a1501cac22acd987c98e867b44e90051c Mon Sep 17 00:00:00 2001 From: Joey Zhao <5253430+joeyzhao2018@users.noreply.github.com> Date: Tue, 21 May 2024 14:29:32 -0400 Subject: [PATCH 086/104] fix(tracing) lazy importing asyncio should not reset existing trace context (#9272) This PR introduces two main changes: 1. Do not reset `_DD_CONTEXTVAR` when initializing a `DefaultContextProvider` 2. Explicitly clear `_DD_CONTEXTVAR` after every test in dd-trace-py CI 1. We also emit a warning, so pytest will show them to us ## Motivation Currently, when 1. asyncio auto instrumentation is patched 5. and there's an existing trace context 6. and `asyncio` is imported Then the existing trace context will be reset to None. The expected behavior is that the existing trace context should not be reset for this kind of lazy imports ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [ ] Title is accurate - [ ] All changes are related to the pull request's stated goal - [ ] Description motivates each change - [ ] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [ ] Testing strategy adequately addresses listed risks - [ ] Change is maintainable (easy to change, telemetry, documentation) - [ ] Release note makes sense to a user of the library - [ ] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [ ] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Brett Langdon --- ddtrace/_trace/provider.py | 1 - ...asyncio-context-reset-6283f01dc7d46dc4.yaml | 4 ++++ tests/conftest.py | 13 +++++++++++++ tests/contrib/asyncio/test_lazyimport.py | 18 ++++++++++++++++++ 4 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/fix-asyncio-context-reset-6283f01dc7d46dc4.yaml create mode 100644 tests/contrib/asyncio/test_lazyimport.py diff --git a/ddtrace/_trace/provider.py b/ddtrace/_trace/provider.py index f56247b8e1a..26b4e73a0af 100644 --- a/ddtrace/_trace/provider.py +++ b/ddtrace/_trace/provider.py @@ -113,7 +113,6 @@ class DefaultContextProvider(BaseContextProvider, DatadogContextMixin): def __init__(self): # type: () -> None super(DefaultContextProvider, self).__init__() - _DD_CONTEXTVAR.set(None) def _has_active_context(self): # type: () -> bool diff --git a/releasenotes/notes/fix-asyncio-context-reset-6283f01dc7d46dc4.yaml b/releasenotes/notes/fix-asyncio-context-reset-6283f01dc7d46dc4.yaml new file mode 100644 index 00000000000..2c9d7f65c58 --- /dev/null +++ b/releasenotes/notes/fix-asyncio-context-reset-6283f01dc7d46dc4.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + This fix resolves an issue where importing ``asyncio`` after a trace has already been started will reset the currently active span. diff --git a/tests/conftest.py b/tests/conftest.py index 9a553821deb..94ba9e5e2e2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -16,12 +16,14 @@ from typing import Generator # noqa:F401 from typing import Tuple # noqa:F401 from unittest import mock +import warnings from _pytest.runner import call_and_report from _pytest.runner import pytest_runtest_protocol as default_pytest_runtest_protocol import pytest import ddtrace +from ddtrace._trace.provider import _DD_CONTEXTVAR from ddtrace.internal.compat import httplib from ddtrace.internal.compat import parse from ddtrace.internal.remoteconfig.client import RemoteConfigClient @@ -67,6 +69,17 @@ def test_spans(tracer): container.reset() +@pytest.fixture(autouse=True) +def clear_context_after_every_test(): + try: + yield + finally: + ctx = _DD_CONTEXTVAR.get() + if ctx is not None: + warnings.warn(f"Context was not cleared after test, expected None, got {ctx}") + _DD_CONTEXTVAR.set(None) + + @pytest.fixture def run_python_code_in_subprocess(tmpdir): def _run(code, **kwargs): diff --git a/tests/contrib/asyncio/test_lazyimport.py b/tests/contrib/asyncio/test_lazyimport.py new file mode 100644 index 00000000000..adca84973db --- /dev/null +++ b/tests/contrib/asyncio/test_lazyimport.py @@ -0,0 +1,18 @@ +import pytest # noqa: I001 + + +@pytest.mark.subprocess() +def test_lazy_import(): + import ddtrace.auto # noqa: F401,I001 + from ddtrace import tracer # noqa: I001 + + assert tracer.context_provider.active() is None + span = tracer.trace("itsatest", service="test", resource="resource", span_type="http") + assert tracer.context_provider.active() is span + + # Importing asyncio after starting a trace does not remove the current active span + import asyncio # noqa: F401 + + assert tracer.context_provider.active() is span + span.finish() + assert tracer.context_provider.active() is None From 719c561f5d4e3bfa0e39ca6b1f087cf2802192f4 Mon Sep 17 00:00:00 2001 From: Federico Mon Date: Tue, 21 May 2024 21:09:35 +0200 Subject: [PATCH 087/104] ci: parametrized framework tests (#9315) CI: parametrize framework tests to run them separately with Profiling, IAST or Appsec enabled ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Alberto Vara --- .github/workflows/test_frameworks.yml | 253 +++++++++++++++++++++++--- 1 file changed, 226 insertions(+), 27 deletions(-) diff --git a/.github/workflows/test_frameworks.yml b/.github/workflows/test_frameworks.yml index 4255f5965d5..0ad718b2520 100644 --- a/.github/workflows/test_frameworks.yml +++ b/.github/workflows/test_frameworks.yml @@ -26,11 +26,28 @@ jobs: continue-on-error: true bottle-testsuite-0_12_19: + strategy: + matrix: + include: + - suffix: Profiling + profiling: 1 + iast: 0 + appsec: 0 + - suffix: IAST + profiling: 0 + iast: 1 + appsec: 0 + - suffix: APPSEC + profiling: 0 + iast: 0 + appsec: 1 + name: Bottle 0.12.19 (with ${{ matrix.suffix }}) runs-on: ubuntu-latest needs: needs-run env: - DD_PROFILING_ENABLED: true - DD_IAST_ENABLED: true + DD_PROFILING_ENABLED: ${{ matrix.profiling }} + DD_IAST_ENABLED: ${{ matrix.iast }} + DD_APPSEC_ENABLED: ${{ matrix.appsec }} DD_TESTING_RAISE: true CMAKE_BUILD_PARALLEL_LEVEL: 12 DD_DEBUGGER_EXPL_OUTPUT_FILE: debugger-expl.txt @@ -121,16 +138,37 @@ jobs: - suffix: DI profiler expl_profiler: 1 expl_coverage: 0 + profiling: 1 + iast: 0 + appsec: 0 - suffix: DI coverage expl_profiler: 0 expl_coverage: 1 + profiling: 1 + iast: 0 + appsec: 0 + # Disabled while the bug is investigated: APPSEC-53222 + # - suffix: IAST + # expl_profiler: 0 + # expl_coverage: 0 + # profiling: 0 + # iast: 1 + # appsec: 0 + - suffix: APPSEC + expl_profiler: 0 + expl_coverage: 0 + profiling: 0 + iast: 0 + appsec: 1 + runs-on: ubuntu-latest needs: needs-run timeout-minutes: 15 name: Django 3.1 (with ${{ matrix.suffix }}) env: - DD_PROFILING_ENABLED: true - DD_IAST_ENABLED: true + DD_PROFILING_ENABLED: ${{ matrix.profiling }} + DD_IAST_ENABLED: ${{ matrix.iast }} + DD_APPSEC_ENABLED: ${{ matrix.appsec }} DD_TESTING_RAISE: true DD_DEBUGGER_EXPL_ENCODE: 0 # Disabled to speed up DD_DEBUGGER_EXPL_PROFILER_ENABLED: ${{ matrix.expl_profiler }} @@ -171,7 +209,9 @@ jobs: run: pip install -r tests/requirements/py3.txt - name: Install ddtrace if: needs.needs-run.outputs.outcome == 'success' - run: pip install ../ddtrace + run: | + pip install envier Cython cmake + pip install ../ddtrace - name: Install django if: needs.needs-run.outputs.outcome == 'success' run: pip install -e . @@ -195,19 +235,35 @@ jobs: - name: Run tests if: needs.needs-run.outputs.outcome == 'success' # django.tests.requests module interferes with requests library patching in the tracer -> disable requests patch - run: DD_PATCH_MODULES=unittest:no DD_TRACE_REQUESTS_ENABLED=0 ddtrace-run tests/runtests.py + run: DD_PATCH_MODULES=unittest:no DD_TRACE_REQUESTS_ENABLED=0 ddtrace-run tests/runtests.py --parallel 1 - name: Debugger exploration results if: needs.needs-run.outputs.outcome == 'success' run: cat debugger-expl.txt graphene-testsuite-3_0: + strategy: + matrix: + include: + - suffix: Profiling + profiling: 1 + iast: 0 + appsec: 0 + - suffix: IAST + profiling: 0 + iast: 1 + appsec: 0 + - suffix: APPSEC + profiling: 0 + iast: 0 + appsec: 1 + name: Graphene 3.0 (with ${{ matrix.suffix }}) runs-on: ubuntu-latest needs: needs-run env: - DD_PROFILING_ENABLED: true - DD_IAST_ENABLED: true - DD_TESTING_RAISE: true + DD_PROFILING_ENABLED: ${{ matrix.profiling }} + DD_IAST_ENABLED: ${{ matrix.iast }} + DD_APPSEC_ENABLED: ${{ matrix.appsec }} PYTHONPATH: ../ddtrace/tests/debugging/exploration/:. DD_DEBUGGER_EXPL_OUTPUT_FILE: debugger-expl.txt CMAKE_BUILD_PARALLEL_LEVEL: 12 @@ -250,12 +306,29 @@ jobs: run: cat debugger-expl.txt fastapi-testsuite-0_92: + strategy: + matrix: + include: + - suffix: Profiling + profiling: 1 + iast: 0 + appsec: 0 + - suffix: IAST + profiling: 0 + iast: 1 + appsec: 0 + - suffix: APPSEC + profiling: 0 + iast: 0 + appsec: 1 + name: FastAPI 0.92 (with ${{ matrix.suffix }}) runs-on: ubuntu-latest needs: needs-run env: DD_TESTING_RAISE: true - DD_PROFILING_ENABLED: true - DD_IAST_ENABLED: true + DD_PROFILING_ENABLED: ${{ matrix.profiling }} + DD_IAST_ENABLED: ${{ matrix.iast }} + DD_APPSEC_ENABLED: ${{ matrix.appsec }} CMAKE_BUILD_PARALLEL_LEVEL: 12 DD_DEBUGGER_EXPL_OUTPUT_FILE: debugger-expl.txt defaults: @@ -297,13 +370,30 @@ jobs: run: cat debugger-expl.txt flask-testsuite-1_1_4: + strategy: + matrix: + include: + - suffix: Profiling + profiling: 1 + iast: 0 + appsec: 0 + - suffix: IAST + profiling: 0 + iast: 1 + appsec: 0 + - suffix: APPSEC + profiling: 0 + iast: 0 + appsec: 1 + name: Flask 1.1.4 (with ${{ matrix.suffix }}) runs-on: ubuntu-latest needs: needs-run env: TOX_TESTENV_PASSENV: DD_TESTING_RAISE DD_PROFILING_ENABLED DD_TESTING_RAISE: true - DD_PROFILING_ENABLED: true - DD_IAST_ENABLED: true + DD_PROFILING_ENABLED: ${{ matrix.profiling }} + DD_IAST_ENABLED: ${{ matrix.iast }} + DD_APPSEC_ENABLED: ${{ matrix.appsec }} PYTHONPATH: ../ddtrace/tests/debugging/exploration/ DD_DEBUGGER_EXPL_OUTPUT_FILE: debugger-expl.txt CMAKE_BUILD_PARALLEL_LEVEL: 12 @@ -349,6 +439,16 @@ jobs: run: cat debugger-expl.txt httpx-testsuite-0_22_0: + strategy: + matrix: + include: + - suffix: IAST + iast: 1 + appsec: 0 + - suffix: APPSEC + iast: 0 + appsec: 1 + name: Httpx 0.22.0 (with ${{ matrix.suffix }}) runs-on: ubuntu-latest needs: needs-run defaults: @@ -385,17 +485,36 @@ jobs: DD_HTTPX_DISTRIBUTED_TRACING: "false" # Debugger exploration testing does not work in CI # PYTHONPATH: ../ddtrace/tests/debugging/exploration/ + DD_IAST_ENABLED: ${{ matrix.iast }} + DD_APPSEC_ENABLED: ${{ matrix.appsec }} # test_pool_timeout raises RuntimeError: The connection pool was closed while 1 HTTP requests/responses were still in-flight run: pytest -k 'not test_pool_timeout' mako-testsuite-1_3_0: + strategy: + matrix: + include: + - suffix: Profiling + profiling: 1 + iast: 0 + appsec: 0 + - suffix: IAST + profiling: 0 + iast: 1 + appsec: 0 + - suffix: APPSEC + profiling: 0 + iast: 0 + appsec: 1 + name: Mako 1.3.0 (with ${{ matrix.suffix }}) runs-on: ubuntu-latest needs: needs-run env: TOX_TESTENV_PASSENV: DD_TESTING_RAISE DD_PROFILING_ENABLED DD_TESTING_RAISE: true - DD_PROFILING_ENABLED: true - DD_IAST_ENABLED: true + DD_PROFILING_ENABLED: ${{ matrix.profiling }} + DD_IAST_ENABLED: ${{ matrix.iast }} + DD_APPSEC_ENABLED: ${{ matrix.appsec }} PYTHONPATH: ../ddtrace/tests/debugging/exploration/ DD_DEBUGGER_EXPL_OUTPUT_FILE: debugger-expl.txt CMAKE_BUILD_PARALLEL_LEVEL: 12 @@ -441,12 +560,30 @@ jobs: run: cat debugger-expl.txt starlette-testsuite-0_37_1: + strategy: + matrix: + include: + - suffix: Profiling + profiling: 1 + iast: 0 + appsec: 0 + - suffix: IAST + profiling: 0 + iast: 1 + appsec: 0 + # Disabled while the bug is investigated: APPSEC-53221 + # - suffix: APPSEC + # profiling: 0 + # iast: 0 + # appsec: 1 + name: Starlette 0.37.1 (with ${{ matrix.suffix }}) runs-on: "ubuntu-latest" needs: needs-run env: DD_TESTING_RAISE: true - DD_PROFILING_ENABLED: true - DD_IAST_ENABLED: true + DD_PROFILING_ENABLED: ${{ matrix.profiling }} + DD_IAST_ENABLED: ${{ matrix.iast }} + DD_APPSEC_ENABLED: ${{ matrix.appsec }} PYTHONPATH: ../ddtrace/tests/debugging/exploration/ DD_DEBUGGER_EXPL_OUTPUT_FILE: debugger-expl.txt CMAKE_BUILD_PARALLEL_LEVEL: 12 @@ -470,7 +607,9 @@ jobs: path: starlette - name: Install ddtrace if: needs.needs-run.outputs.outcome == 'success' - run: pip install ../ddtrace + run: | + pip install envier Cython cmake + pip install ../ddtrace - name: Install dependencies if: needs.needs-run.outputs.outcome == 'success' run: scripts/install @@ -484,12 +623,29 @@ jobs: run: cat debugger-expl.txt requests-testsuite-2_26_0: + strategy: + matrix: + include: + - suffix: Profiling + profiling: 1 + iast: 0 + appsec: 0 + - suffix: IAST + profiling: 0 + iast: 1 + appsec: 0 + - suffix: APPSEC + profiling: 0 + iast: 0 + appsec: 1 + name: Requests 2.26.0 (with ${{ matrix.suffix }}) runs-on: "ubuntu-latest" needs: needs-run env: DD_TESTING_RAISE: true - DD_PROFILING_ENABLED: true - DD_IAST_ENABLED: true + DD_PROFILING_ENABLED: ${{ matrix.profiling }} + DD_IAST_ENABLED: ${{ matrix.iast }} + DD_APPSEC_ENABLED: ${{ matrix.appsec }} CMAKE_BUILD_PARALLEL_LEVEL: 12 DD_DEBUGGER_EXPL_OUTPUT_FILE: debugger-expl.txt defaults: @@ -528,12 +684,29 @@ jobs: asyncpg-testsuite-0_27_0: # https://github.com/MagicStack/asyncpg/blob/v0.25.0/.github/workflows/tests.yml#L125 + strategy: + matrix: + include: + - suffix: Profiling + profiling: 1 + iast: 0 + appsec: 0 + - suffix: IAST + profiling: 0 + iast: 1 + appsec: 0 + - suffix: APPSEC + profiling: 0 + iast: 0 + appsec: 1 + name: AsyncPG 0.27.0 (with ${{ matrix.suffix }}) runs-on: "ubuntu-latest" needs: needs-run env: DD_TESTING_RAISE: true - DD_PROFILING_ENABLED: true - DD_IAST_ENABLED: true + DD_PROFILING_ENABLED: ${{ matrix.profiling }} + DD_IAST_ENABLED: ${{ matrix.iast }} + DD_APPSEC_ENABLED: ${{ matrix.appsec }} CMAKE_BUILD_PARALLEL_LEVEL: 12 defaults: run: @@ -569,12 +742,22 @@ jobs: run: ddtrace-run python -m pytest -k 'not test_record_gc and not test_record_get and not test_record_items and not test_record_iter' tests gunicorn-testsuite-20_1_0: - name: gunicorn 20.1.0 + strategy: + matrix: + include: + - suffix: IAST + iast: 1 + appsec: 0 + - suffix: APPSEC + iast: 0 + appsec: 1 + name: gunicorn 20.1.0 (with ${{ matrix.suffix }}) runs-on: "ubuntu-latest" needs: needs-run env: DD_TESTING_RAISE: true - DD_IAST_ENABLED: true + DD_IAST_ENABLED: ${{ matrix.iast }} + DD_APPSEC_ENABLED: ${{ matrix.appsec }} # PYTHONPATH: ../ddtrace/tests/debugging/exploration/ CMAKE_BUILD_PARALLEL_LEVEL: 12 defaults: @@ -604,13 +787,29 @@ jobs: pytest -p no:warnings -k "not test_import" tests/ uwsgi-testsuite-2_0_21: - name: uwsgi 2.0.21 + strategy: + matrix: + include: + - suffix: Profiling + profiling: 1 + iast: 0 + appsec: 0 + - suffix: IAST + profiling: 0 + iast: 1 + appsec: 0 + - suffix: APPSEC + profiling: 0 + iast: 0 + appsec: 1 + name: uwsgi 2.0.21 (with ${{ matrix.suffix }}) runs-on: "ubuntu-latest" needs: needs-run env: DD_TESTING_RAISE: true - DD_PROFILING_ENABLED: true - DD_IAST_ENABLED: true + DD_PROFILING_ENABLED: ${{ matrix.profiling }} + DD_IAST_ENABLED: ${{ matrix.iast }} + DD_APPSEC_ENABLED: ${{ matrix.appsec }} PYTHONPATH: ../ddtrace/tests/debugging/exploration/ DD_DEBUGGER_EXPL_OUTPUT_FILE: debugger-expl.txt CMAKE_BUILD_PARALLEL_LEVEL: 12 From 52b0c59a311bf3977e5c0e69dc2f352ba74ef283 Mon Sep 17 00:00:00 2001 From: Yun Kim <35776586+Yun-Kim@users.noreply.github.com> Date: Tue, 21 May 2024 15:42:19 -0400 Subject: [PATCH 088/104] fix(openai): remove patch code for removed API endpoints (#9331) Unblocks #9321. This PR removes patching for the `edits` and `fine tunes` OpenAI API endpoints. These endpoints have been removed entirely from the OpenAI API as of January 2024, and are not available to users even when using older versions of the OpenAI SDK / API. Things removed by this PR: - Patch code for removed endpoints - Tests (`test_openai_v0.py`, `test_openai_v1.py`) - Snapshot files for removed tests - Cassette files for removed tests ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/contrib/openai/_endpoint_hooks.py | 149 --- ddtrace/contrib/openai/patch.py | 27 - ...move-edit-fine-tunes-0fc21266ef1cddb0.yaml | 5 + tests/contrib/openai/cassettes/v0/edit.yaml | 75 -- .../openai/cassettes/v0/edit_async.yaml | 69 -- .../openai/cassettes/v0/fine_tune_cancel.yaml | 66 -- .../cassettes/v0/fine_tune_cancel_async.yaml | 64 -- .../openai/cassettes/v0/fine_tune_create.yaml | 70 -- .../cassettes/v0/fine_tune_create_async.yaml | 66 -- .../openai/cassettes/v0/fine_tune_list.yaml | 167 ---- .../cassettes/v0/fine_tune_list_async.yaml | 872 ----------------- .../cassettes/v0/fine_tune_list_events.yaml | 61 -- .../cassettes/v0/fine_tune_retrieve.yaml | 64 -- .../v0/fine_tune_retrieve_async.yaml | 64 -- tests/contrib/openai/cassettes/v1/edit.yaml | 84 -- .../openai/cassettes/v1/edit_async.yaml | 84 -- .../openai/cassettes/v1/fine_tune_cancel.yaml | 80 -- .../openai/cassettes/v1/fine_tune_create.yaml | 80 -- .../openai/cassettes/v1/fine_tune_list.yaml | 878 ------------------ .../cassettes/v1/fine_tune_list_events.yaml | 69 -- .../cassettes/v1/fine_tune_retrieve.yaml | 78 -- tests/contrib/openai/test_openai_v0.py | 253 ----- tests/contrib/openai/test_openai_v1.py | 230 ----- ....contrib.openai.test_openai.test_edit.json | 49 - ...nai.test_openai.test_fine_tune_cancel.json | 49 - ...nai.test_openai.test_fine_tune_create.json | 59 -- ...penai.test_openai.test_fine_tune_list.json | 35 - ...est_openai.test_fine_tune_list_events.json | 37 - ...i.test_openai.test_fine_tune_retrieve.json | 49 - 29 files changed, 5 insertions(+), 3928 deletions(-) create mode 100644 releasenotes/notes/fix-openai-remove-edit-fine-tunes-0fc21266ef1cddb0.yaml delete mode 100644 tests/contrib/openai/cassettes/v0/edit.yaml delete mode 100644 tests/contrib/openai/cassettes/v0/edit_async.yaml delete mode 100644 tests/contrib/openai/cassettes/v0/fine_tune_cancel.yaml delete mode 100644 tests/contrib/openai/cassettes/v0/fine_tune_cancel_async.yaml delete mode 100644 tests/contrib/openai/cassettes/v0/fine_tune_create.yaml delete mode 100644 tests/contrib/openai/cassettes/v0/fine_tune_create_async.yaml delete mode 100644 tests/contrib/openai/cassettes/v0/fine_tune_list.yaml delete mode 100644 tests/contrib/openai/cassettes/v0/fine_tune_list_async.yaml delete mode 100644 tests/contrib/openai/cassettes/v0/fine_tune_list_events.yaml delete mode 100644 tests/contrib/openai/cassettes/v0/fine_tune_retrieve.yaml delete mode 100644 tests/contrib/openai/cassettes/v0/fine_tune_retrieve_async.yaml delete mode 100644 tests/contrib/openai/cassettes/v1/edit.yaml delete mode 100644 tests/contrib/openai/cassettes/v1/edit_async.yaml delete mode 100644 tests/contrib/openai/cassettes/v1/fine_tune_cancel.yaml delete mode 100644 tests/contrib/openai/cassettes/v1/fine_tune_create.yaml delete mode 100644 tests/contrib/openai/cassettes/v1/fine_tune_list.yaml delete mode 100644 tests/contrib/openai/cassettes/v1/fine_tune_list_events.yaml delete mode 100644 tests/contrib/openai/cassettes/v1/fine_tune_retrieve.yaml delete mode 100644 tests/snapshots/tests.contrib.openai.test_openai.test_edit.json delete mode 100644 tests/snapshots/tests.contrib.openai.test_openai.test_fine_tune_cancel.json delete mode 100644 tests/snapshots/tests.contrib.openai.test_openai.test_fine_tune_create.json delete mode 100644 tests/snapshots/tests.contrib.openai.test_openai.test_fine_tune_list.json delete mode 100644 tests/snapshots/tests.contrib.openai.test_openai.test_fine_tune_list_events.json delete mode 100644 tests/snapshots/tests.contrib.openai.test_openai.test_fine_tune_retrieve.json diff --git a/ddtrace/contrib/openai/_endpoint_hooks.py b/ddtrace/contrib/openai/_endpoint_hooks.py index 53e11344994..fff7e029901 100644 --- a/ddtrace/contrib/openai/_endpoint_hooks.py +++ b/ddtrace/contrib/openai/_endpoint_hooks.py @@ -359,8 +359,6 @@ def _record_request(self, pin, integration, span, args, kwargs): span.resource = "listModels" elif endpoint.endswith("/files"): span.resource = "listFiles" - elif endpoint.endswith("/fine-tunes"): - span.resource = "listFineTunes" def _record_response(self, pin, integration, span, args, kwargs, resp, error): resp = super()._record_response(pin, integration, span, args, kwargs, resp, error) @@ -388,15 +386,6 @@ class _FileListHook(_ListHook): OPERATION_ID = "listFiles" -class _FineTuneListHook(_ListHook): - """ - Hook for openai.resources.fine_tunes.FineTunes.list (v1) - """ - - ENDPOINT_NAME = "fine-tunes" - OPERATION_ID = "listFineTunes" - - class _RetrieveHook(_EndpointHook): """Hook for openai.APIResource, which is used by Model.retrieve, File.retrieve, and FineTune.retrieve.""" @@ -431,11 +420,6 @@ def _record_request(self, pin, integration, span, args, kwargs): elif endpoint.endswith("/files"): span.resource = "retrieveFile" span.set_tag_str("openai.request.file_id", args[1] if len(args) >= 2 else kwargs.get("file_id", "")) - elif endpoint.endswith("/fine-tunes"): - span.resource = "retrieveFineTune" - span.set_tag_str( - "openai.request.fine_tune_id", args[1] if len(args) >= 2 else kwargs.get("fine_tune_id", "") - ) span.set_tag_str("openai.request.endpoint", "%s/*" % endpoint) def _record_response(self, pin, integration, span, args, kwargs, resp, error): @@ -480,19 +464,6 @@ def _record_request(self, pin, integration, span, args, kwargs): span.set_tag_str("openai.request.file_id", args[1] if len(args) >= 2 else kwargs.get("file_id", "")) -class _FineTuneRetrieveHook(_RetrieveHook): - """ - Hook for openai.resources.fine_tunes.FineTunes.retrieve - """ - - ENDPOINT_NAME = "fine-tunes" - OPERATION_ID = "retrieveFineTune" - - def _record_request(self, pin, integration, span, args, kwargs): - super()._record_request(pin, integration, span, args, kwargs) - span.set_tag_str("openai.request.fine_tune_id", args[1] if len(args) >= 2 else kwargs.get("fine_tune_id", "")) - - class _DeleteHook(_EndpointHook): """Hook for openai.DeletableAPIResource, which is used by File.delete, and Model.delete.""" @@ -544,49 +515,6 @@ class _ModelDeleteHook(_DeleteHook): ENDPOINT_NAME = "models" -class _EditHook(_EndpointHook): - _request_arg_params = ("api_key", "api_base", "api_type", "request_id", "api_version", "organization") - _request_kwarg_params = ("model", "n", "temperature", "top_p", "user") - _response_attrs = ("created",) - ENDPOINT_NAME = "edits" - HTTP_METHOD_TYPE = "POST" - OPERATION_ID = "createEdit" - - def _record_request(self, pin, integration, span, args, kwargs): - super()._record_request(pin, integration, span, args, kwargs) - if integration.is_pc_sampled_span(span): - instruction = kwargs.get("instruction") - input_text = kwargs.get("input", "") - span.set_tag_str("openai.request.instruction", integration.trunc(instruction)) - span.set_tag_str("openai.request.input", integration.trunc(input_text)) - - def _record_response(self, pin, integration, span, args, kwargs, resp, error): - resp = super()._record_response(pin, integration, span, args, kwargs, resp, error) - if integration.is_pc_sampled_log(span): - log_choices = resp.choices - if hasattr(resp.choices[0], "model_dump"): - log_choices = [choice.model_dump() for choice in resp.choices] - integration.log( - span, - "info" if error is None else "error", - "sampled %s" % self.OPERATION_ID, - attrs={ - "instruction": kwargs.get("instruction"), - "input": kwargs.get("input", ""), - "choices": log_choices, - }, - ) - if not resp: - return - choices = resp.choices - if integration.is_pc_sampled_span(span): - for choice in choices: - idx = choice.index - span.set_tag_str("openai.response.choices.%d.text" % idx, integration.trunc(str(choice.text))) - integration.record_usage(span, resp.usage) - return resp - - class _ImageHook(_EndpointHook): _response_attrs = ("created",) ENDPOINT_NAME = "images" @@ -839,80 +767,3 @@ def _record_response(self, pin, integration, span, args, kwargs, resp, error): else: span.set_metric("openai.response.total_bytes", getattr(resp, "total_bytes", 0)) return resp - - -class _BaseFineTuneHook(_EndpointHook): - _response_attrs = ("id", "model", "fine_tuned_model", "status", "created_at", "updated_at") - - def _record_response(self, pin, integration, span, args, kwargs, resp, error): - resp = super()._record_response(pin, integration, span, args, kwargs, resp, error) - if not resp: - return - span.set_metric("openai.response.events_count", len(resp.events)) - span.set_metric("openai.response.result_files_count", len(resp.result_files)) - span.set_metric("openai.response.training_files_count", len(resp.training_files)) - span.set_metric("openai.response.validation_files_count", len(resp.validation_files)) - hyperparams = resp.hyperparams - for hyperparam in ("batch_size", "learning_rate_multiplier", "n_epochs", "prompt_loss_weight"): - span.set_tag_str("openai.response.hyperparams.%s" % hyperparam, str(getattr(hyperparams, hyperparam, ""))) - - return resp - - -class _FineTuneCreateHook(_BaseFineTuneHook): - _request_arg_params = ("api_key", "api_base", "api_type", "request_id", "api_version", "organization") - _request_kwarg_params = ( - "training_file", - "validation_file", - "model", - "n_epochs", - "batch_size", - "learning_rate_multiplier", - "prompt_loss_weight", - "compute_classification_metrics", - "classification_n_classes", - "classification_positive_class", - "suffix", - ) - ENDPOINT_NAME = "fine-tunes" - HTTP_METHOD_TYPE = "POST" - OPERATION_ID = "createFineTune" - - def _record_request(self, pin, integration, span, args, kwargs): - super()._record_request(pin, integration, span, args, kwargs) - if "classification_betas" in kwargs: - classification_betas = kwargs.get("classification_betas", []) - if classification_betas: - span.set_metric("openai.request.classification_betas_count", len(classification_betas)) - else: - span.set_metric("openai.request.classification_betas_count", 0) - - -class _FineTuneCancelHook(_BaseFineTuneHook): - _request_arg_params = (None, "api_key", "api_type", "request_id", "api_version") - _request_kwarg_params = ("user",) - ENDPOINT_NAME = "fine-tunes/*/cancel" - HTTP_METHOD_TYPE = "POST" - OPERATION_ID = "cancelFineTune" - - def _record_request(self, pin, integration, span, args, kwargs): - super()._record_request(pin, integration, span, args, kwargs) - span.set_tag_str("openai.request.fine_tune_id", args[1] if len(args) >= 2 else kwargs.get("fine_tune_id", "")) - - -class _FineTuneListEventsHook(_EndpointHook): - _request_kwarg_params = ("stream", "user") - ENDPOINT_NAME = "fine-tunes/*/events" - HTTP_METHOD_TYPE = "GET" - OPERATION_ID = "listFineTuneEvents" - - def _record_request(self, pin, integration, span, args, kwargs): - super()._record_request(pin, integration, span, args, kwargs) - span.set_tag_str("openai.request.fine_tune_id", args[1] if len(args) >= 2 else kwargs.get("fine_tune_id", "")) - - def _record_response(self, pin, integration, span, args, kwargs, resp, error): - resp = super()._record_response(pin, integration, span, args, kwargs, resp, error) - if not resp: - return - span.set_metric("openai.response.count", len(resp.data)) - return resp diff --git a/ddtrace/contrib/openai/patch.py b/ddtrace/contrib/openai/patch.py index aca4693ede2..5e3bf2caead 100644 --- a/ddtrace/contrib/openai/patch.py +++ b/ddtrace/contrib/openai/patch.py @@ -53,9 +53,6 @@ def get_version(): "chat.Completions": { "create": _endpoint_hooks._ChatCompletionHook, }, - "edits.Edits": { - "create": _endpoint_hooks._EditHook, - }, "images.Images": { "generate": _endpoint_hooks._ImageCreateHook, "edit": _endpoint_hooks._ImageEditHook, @@ -80,13 +77,6 @@ def get_version(): "delete": _endpoint_hooks._FileDeleteHook, "retrieve_content": _endpoint_hooks._FileDownloadHook, }, - "fine_tunes.FineTunes": { - "create": _endpoint_hooks._FineTuneCreateHook, - "retrieve": _endpoint_hooks._FineTuneRetrieveHook, - "list": _endpoint_hooks._FineTuneListHook, - "cancel": _endpoint_hooks._FineTuneCancelHook, - "list_events": _endpoint_hooks._FineTuneListEventsHook, - }, } else: _RESOURCES = { @@ -100,9 +90,6 @@ def get_version(): "chat_completion.ChatCompletion": { "create": _endpoint_hooks._ChatCompletionHook, }, - "edit.Edit": { - "create": _endpoint_hooks._EditHook, - }, "image.Image": { "create": _endpoint_hooks._ImageCreateHook, "create_edit": _endpoint_hooks._ImageEditHook, @@ -125,14 +112,6 @@ def get_version(): "delete": _endpoint_hooks._DeleteHook, "download": _endpoint_hooks._FileDownloadHook, }, - "fine_tune.FineTune": { - # FineTune.list()/retrieve() share the same underlying method as Model.list() and Model.retrieve() - # FineTune.delete() share the same underlying method as File.delete() - # which means they are already wrapped - # FineTune.list_events does not have an async version, so have to wrap it separately - "create": _endpoint_hooks._FineTuneCreateHook, - "cancel": _endpoint_hooks._FineTuneCancelHook, - }, } @@ -186,12 +165,6 @@ def patch(): _wrap_classmethod(sync_method, _patched_endpoint(openai, integration, endpoint_hook)) _wrap_classmethod(async_method, _patched_endpoint_async(openai, integration, endpoint_hook)) - # FineTune.list_events is the only traced endpoint that does not have an async version, so have to wrap it here. - _wrap_classmethod( - openai.api_resources.fine_tune.FineTune.list_events, - _patched_endpoint(openai, integration, _endpoint_hooks._FineTuneListEventsHook), - ) - openai.__datadog_patch = True diff --git a/releasenotes/notes/fix-openai-remove-edit-fine-tunes-0fc21266ef1cddb0.yaml b/releasenotes/notes/fix-openai-remove-edit-fine-tunes-0fc21266ef1cddb0.yaml new file mode 100644 index 00000000000..c7909c9a0a9 --- /dev/null +++ b/releasenotes/notes/fix-openai-remove-edit-fine-tunes-0fc21266ef1cddb0.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + openai: This fix removes patching for the edits and fine tunes endpoints, which have been removed + from the OpenAI API. diff --git a/tests/contrib/openai/cassettes/v0/edit.yaml b/tests/contrib/openai/cassettes/v0/edit.yaml deleted file mode 100644 index 0464d1011c3..00000000000 --- a/tests/contrib/openai/cassettes/v0/edit.yaml +++ /dev/null @@ -1,75 +0,0 @@ -interactions: -- request: - body: '{"model": "text-davinci-edit-001", "input": "thsi si a spelilgn imstkae.", - "instruction": "fix spelling mistakes", "n": 3, "temperature": 0.2}' - headers: - Accept: - - '*/*' - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - Content-Length: - - '142' - Content-Type: - - application/json - User-Agent: - - OpenAI/v1 PythonBindings/0.27.7 - X-OpenAI-Client-User-Agent: - - '{"bindings_version": "0.27.7", "httplib": "requests", "lang": "python", "lang_version": - "3.10.5", "platform": "macOS-13.3.1-arm64-arm-64bit", "publisher": "openai", - "uname": "Darwin 22.4.0 Darwin Kernel Version 22.4.0: Mon Mar 6 20:59:28 - PST 2023; root:xnu-8796.101.5~3/RELEASE_ARM64_T6000 arm64"}' - method: POST - uri: https://api.openai.com/v1/edits - response: - body: - string: !!binary | - H4sIAAAAAAAAA5SPQQqDMBQF9z1FeOtQVEqNuUpbSho/mhqTYH5BkNy9uHHvdphZzIb4+ZJlaFDv - GBJ2IcPUQ9d3dWtVp1olYcfoLGXoxwamddd5dFm4LIzIibx3YRCzy2wmuj4DJFzoaYWuijyb1OeT - prwkftkMBL0hLXFO/OY4UcjQzT4Q5+SJXQwH7ioJjmz8QepalXL5AwAA//8DANrtHxcTAQAA - headers: - CF-Cache-Status: - - DYNAMIC - CF-RAY: - - 7cb80d055891424d-EWR - Cache-Control: - - no-cache, must-revalidate - Connection: - - keep-alive - Content-Encoding: - - gzip - Content-Type: - - application/json - Date: - - Mon, 22 May 2023 21:11:19 GMT - Server: - - cloudflare - Transfer-Encoding: - - chunked - access-control-allow-origin: - - '*' - alt-svc: - - h3=":443"; ma=86400, h3-29=":443"; ma=86400 - openai-model: - - text-davinci-edit:001 - openai-organization: - - datadog-4 - openai-processing-ms: - - '538' - openai-version: - - '2020-10-01' - strict-transport-security: - - max-age=15724800; includeSubDomains - x-ratelimit-limit-requests: - - '20' - x-ratelimit-remaining-requests: - - '19' - x-ratelimit-reset-requests: - - 3s - x-request-id: - - 8c74c080e91d3f6360caf258bf76b80e - status: - code: 200 - message: OK -version: 1 diff --git a/tests/contrib/openai/cassettes/v0/edit_async.yaml b/tests/contrib/openai/cassettes/v0/edit_async.yaml deleted file mode 100644 index 4cd90b87e56..00000000000 --- a/tests/contrib/openai/cassettes/v0/edit_async.yaml +++ /dev/null @@ -1,69 +0,0 @@ -interactions: -- request: - body: '{"model": "text-davinci-edit-001", "input": "thsi si a spelilgn imstkae.", - "instruction": "fix spelling mistakes", "n": 3, "top_p": 0.3}' - headers: - Content-Type: - - application/json - User-Agent: - - OpenAI/v1 PythonBindings/0.27.7 - X-OpenAI-Client-User-Agent: - - '{"bindings_version": "0.27.7", "httplib": "requests", "lang": "python", "lang_version": - "3.10.5", "platform": "macOS-13.3.1-arm64-arm-64bit", "publisher": "openai", - "uname": "Darwin 22.4.0 Darwin Kernel Version 22.4.0: Mon Mar 6 20:59:28 - PST 2023; root:xnu-8796.101.5~3/RELEASE_ARM64_T6000 arm64"}' - method: post - uri: https://api.openai.com/v1/edits - response: - body: - string: '{"object":"edit","created":1684789878,"choices":[{"text":"this is a - spelling mistake.\n","index":0},{"text":"this is a spelling mistake.\n","index":1},{"text":"this - is a spelling mistake.\n","index":2}],"usage":{"prompt_tokens":28,"completion_tokens":90,"total_tokens":118}} - - ' - headers: - Access-Control-Allow-Origin: - - '*' - CF-Cache-Status: - - DYNAMIC - CF-RAY: - - 7cb80ad40a128c7e-EWR - Cache-Control: - - no-cache, must-revalidate - Connection: - - keep-alive - Content-Encoding: - - gzip - Content-Type: - - application/json - Date: - - Mon, 22 May 2023 21:09:49 GMT - Server: - - cloudflare - Transfer-Encoding: - - chunked - alt-svc: - - h3=":443"; ma=86400, h3-29=":443"; ma=86400 - openai-model: - - text-davinci-edit:001 - openai-organization: - - datadog-4 - openai-processing-ms: - - '880' - openai-version: - - '2020-10-01' - strict-transport-security: - - max-age=15724800; includeSubDomains - x-ratelimit-limit-requests: - - '20' - x-ratelimit-remaining-requests: - - '19' - x-ratelimit-reset-requests: - - 3s - x-request-id: - - d5773e88c39ac2850c70162cfa98e013 - status: - code: 200 - message: OK - url: https://api.openai.com/v1/edits -version: 1 diff --git a/tests/contrib/openai/cassettes/v0/fine_tune_cancel.yaml b/tests/contrib/openai/cassettes/v0/fine_tune_cancel.yaml deleted file mode 100644 index bec43f8b630..00000000000 --- a/tests/contrib/openai/cassettes/v0/fine_tune_cancel.yaml +++ /dev/null @@ -1,66 +0,0 @@ -interactions: -- request: - body: null - headers: - Accept: - - '*/*' - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - Content-Length: - - '0' - User-Agent: - - OpenAI/v1 PythonBindings/0.27.7 - X-OpenAI-Client-User-Agent: - - '{"bindings_version": "0.27.7", "httplib": "requests", "lang": "python", "lang_version": - "3.10.5", "platform": "macOS-13.4-arm64-arm-64bit", "publisher": "openai", - "uname": "Darwin 22.5.0 Darwin Kernel Version 22.5.0: Mon Apr 24 20:52:24 - PDT 2023; root:xnu-8796.121.2~5/RELEASE_ARM64_T6000 arm64"}' - method: POST - uri: https://api.openai.com/v1/fine-tunes/ft-N6ggcFNqJNuREixR9ShDWzST/cancel - response: - body: - string: !!binary | - H4sIAAAAAAAAA7STX2/aMBTF3/MpLD+TCrpAgLe1gLRJY1qptoeqsoxzSdw5duo/FKj47pXtQDS6 - qU97Pede59zfvXlNEMJq/QTM4inCGy4htU4C7nmDF0G06XJUlmyxfP66dHdzvrubrKrZr8PqPpZV - +wZ0QzWtDZ6i1wQhhLAk0ChWeSXrRWlNLauI4QfAUySdEK3eaFU3lghlDHkBXlY+S/+qP2h9AVRL - LkuiqQVSO2F5Izjo9pUEoWPIoXRJJT9Qy5UkMbvSZfp9lR2YmS3HZjGej2+Lald++33zM4vha1WA - 8KXMad7ObTXl4YMbLsBP8BCCxMkugInYE/SWFxeQCjF77v+YCJrPb+5X7LN6+aLsbse64sbpRhl4 - jz24/hFJ62DvnQxJrp6MkqKrWe9tSDfI8tEkP8tMA7VQEOoTDkbjLMvyYT44+8ZS63yfB8/AGCjw - hUkKsJQL0xFG6Jgg9BjwbKngRaR8BhQdDcYJe6leBhqORp/yfrgK7JrivXc9DF4XlFHJQIg2aOBF - PK+CnNZ3OicMW5D2g521tNNQ280uYBtPgcuN6uQajKFl2MRtnASdn5iij/6Ov66kJRC59v5D0MWp - Ff2J7t9profdlpNj8gYAAP//AwBHMa9kGQQAAA== - headers: - CF-Cache-Status: - - DYNAMIC - CF-RAY: - - 7d0b632c09b4179d-EWR - Connection: - - keep-alive - Content-Encoding: - - gzip - Content-Type: - - application/json - Date: - - Thu, 01 Jun 2023 23:55:25 GMT - Server: - - cloudflare - Transfer-Encoding: - - chunked - access-control-allow-origin: - - '*' - alt-svc: - - h3=":443"; ma=86400 - openai-processing-ms: - - '65' - openai-version: - - '2020-10-01' - strict-transport-security: - - max-age=15724800; includeSubDomains - x-request-id: - - 8fa910feb9c9fc35aaf146fc3368b7aa - status: - code: 200 - message: OK -version: 1 diff --git a/tests/contrib/openai/cassettes/v0/fine_tune_cancel_async.yaml b/tests/contrib/openai/cassettes/v0/fine_tune_cancel_async.yaml deleted file mode 100644 index 7310a717fae..00000000000 --- a/tests/contrib/openai/cassettes/v0/fine_tune_cancel_async.yaml +++ /dev/null @@ -1,64 +0,0 @@ -interactions: -- request: - body: null - headers: - User-Agent: - - OpenAI/v1 PythonBindings/0.27.7 - X-OpenAI-Client-User-Agent: - - '{"bindings_version": "0.27.7", "httplib": "requests", "lang": "python", "lang_version": - "3.10.5", "platform": "macOS-13.4-arm64-arm-64bit", "publisher": "openai", - "uname": "Darwin 22.5.0 Darwin Kernel Version 22.5.0: Mon Apr 24 20:52:24 - PDT 2023; root:xnu-8796.121.2~5/RELEASE_ARM64_T6000 arm64"}' - method: post - uri: https://api.openai.com/v1/fine-tunes/ft-N6ggcFNqJNuREixR9ShDWzST/cancel - response: - body: - string: "{\n \"object\": \"fine-tune\",\n \"id\": \"ft-N6ggcFNqJNuREixR9ShDWzST\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": null,\n \"prompt_loss_weight\": - 0.01,\n \"learning_rate_multiplier\": null\n },\n \"organization_id\": - \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n \"model\": \"curie\",\n \"training_files\": - [\n {\n \"object\": \"file\",\n \"id\": \"file-llDq0Q9la7EBTScAowIotxxc\",\n - \ \"purpose\": \"fine-tune\",\n \"filename\": \"yun_file.jsonl\",\n - \ \"bytes\": 147697,\n \"created_at\": 1684447571,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [],\n \"created_at\": 1685663704,\n \"updated_at\": - 1685663725,\n \"status\": \"cancelled\",\n \"fine_tuned_model\": null,\n - \ \"events\": [\n {\n \"object\": \"fine-tune-event\",\n \"level\": - \"info\",\n \"message\": \"Created fine-tune: ft-N6ggcFNqJNuREixR9ShDWzST\",\n - \ \"created_at\": 1685663704\n },\n {\n \"object\": \"fine-tune-event\",\n - \ \"level\": \"info\",\n \"message\": \"Fine-tune cancelled\",\n - \ \"created_at\": 1685663725\n }\n ]\n}\n" - headers: - Access-Control-Allow-Origin: - - '*' - CF-Cache-Status: - - DYNAMIC - CF-RAY: - - 7d0b6748ebc78ca1-EWR - Connection: - - keep-alive - Content-Encoding: - - gzip - Content-Type: - - application/json - Date: - - Thu, 01 Jun 2023 23:58:14 GMT - Server: - - cloudflare - Transfer-Encoding: - - chunked - alt-svc: - - h3=":443"; ma=86400 - openai-processing-ms: - - '76' - openai-version: - - '2020-10-01' - strict-transport-security: - - max-age=15724800; includeSubDomains - x-request-id: - - 2fdb479bf0e1986e08d51b0d4e3b1854 - status: - code: 200 - message: OK - url: https://api.openai.com/v1/fine-tunes/ft-N6ggcFNqJNuREixR9ShDWzST/cancel -version: 1 diff --git a/tests/contrib/openai/cassettes/v0/fine_tune_create.yaml b/tests/contrib/openai/cassettes/v0/fine_tune_create.yaml deleted file mode 100644 index d0585545fb6..00000000000 --- a/tests/contrib/openai/cassettes/v0/fine_tune_create.yaml +++ /dev/null @@ -1,70 +0,0 @@ -interactions: -- request: - body: '{"training_file": "file-llDq0Q9la7EBTScAowIotxxc", "n_epochs": 4, "prompt_loss_weight": - 0.01, "model": "babbage", "suffix": "dummy-fine-tune-model", "batch_size": 5, - "learning_rate_multiplier": 0.05, "compute_classification_metrics": false}' - headers: - Accept: - - '*/*' - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - Content-Length: - - '240' - Content-Type: - - application/json - User-Agent: - - OpenAI/v1 PythonBindings/0.27.7 - X-OpenAI-Client-User-Agent: - - '{"bindings_version": "0.27.7", "httplib": "requests", "lang": "python", "lang_version": - "3.10.5", "platform": "macOS-13.4-arm64-arm-64bit", "publisher": "openai", - "uname": "Darwin 22.5.0 Darwin Kernel Version 22.5.0: Mon Apr 24 20:52:24 - PDT 2023; root:xnu-8796.121.2~5/RELEASE_ARM64_T6000 arm64"}' - method: POST - uri: https://api.openai.com/v1/fine-tunes - response: - body: - string: !!binary | - H4sIAAAAAAAAA4STX2+bMBTF3/kUFs+lChEEylvbdGon7U+VqppUTZbBN+DU2Mw2IUmV717ZZmHK - NvX1/I7NueeatwChUJYbqExYoHDNBESmFxBeWMCoE020Xa5eH7/vV3F6r8Sm/Tz/MQzlbGDe1uw7 - UB1RpNVhgd4ChBAKBYZOVo1VkgsvlcRUDdbsAGGB0lHslGw7g7nUGg/A6sYGmV3O4pFzIEowUWNF - DOC254Z1nIHyrjRA6OhCSFUTwQ7EMCmwDy5VHX1bJYdKL7/m+lN+l9/SZld/eb15TnzyVlLg1lqS - siT1OLZRhLlPrhkHO8CLi+IHO+uL+zNOH+tiHCLOl79mj1ecZHc3T6vqWg4P0ux21WTuetVJDX+3 - 7qi9RJDW4X0vXJLLjZaCT55yb1y6OMkWV9lJrhQQAxQTmzBe5EmSZGkWn7g2xPT2nK2+Aq2BhmcQ - UzCEcWsSPecOHgOEfrp6toQz6ns+FeSJAt1zc66eB0oXi3k+nzvWd/S/7I+gICgTtd+ObQvbtij+ - vT4b0jHYgjAfbGzsOnLeaXIOW/8UmFjLSW5Ba/swChTe+jnQ6YoCffRr/HMh44xTq8ExeAcAAP// - AwDFbZKdiAMAAA== - headers: - CF-Cache-Status: - - DYNAMIC - CF-RAY: - - 7d0b4d1fca3d8c99-EWR - Connection: - - keep-alive - Content-Encoding: - - gzip - Content-Type: - - application/json - Date: - - Thu, 01 Jun 2023 23:40:22 GMT - Server: - - cloudflare - Transfer-Encoding: - - chunked - access-control-allow-origin: - - '*' - alt-svc: - - h3=":443"; ma=86400 - openai-processing-ms: - - '121' - openai-version: - - '2020-10-01' - strict-transport-security: - - max-age=15724800; includeSubDomains - x-request-id: - - d8506720ee0c9b0d6a9d0df19b0a8907 - status: - code: 200 - message: OK -version: 1 diff --git a/tests/contrib/openai/cassettes/v0/fine_tune_create_async.yaml b/tests/contrib/openai/cassettes/v0/fine_tune_create_async.yaml deleted file mode 100644 index d88224b89ff..00000000000 --- a/tests/contrib/openai/cassettes/v0/fine_tune_create_async.yaml +++ /dev/null @@ -1,66 +0,0 @@ -interactions: -- request: - body: '{"training_file": "file-llDq0Q9la7EBTScAowIotxxc", "n_epochs": 4, "prompt_loss_weight": - 0.01, "model": "babbage", "suffix": "dummy-fine-tune-model", "batch_size": 5, - "learning_rate_multiplier": 0.05, "compute_classification_metrics": false}' - headers: - Content-Type: - - application/json - User-Agent: - - OpenAI/v1 PythonBindings/0.27.7 - X-OpenAI-Client-User-Agent: - - '{"bindings_version": "0.27.7", "httplib": "requests", "lang": "python", "lang_version": - "3.10.5", "platform": "macOS-13.4-arm64-arm-64bit", "publisher": "openai", - "uname": "Darwin 22.5.0 Darwin Kernel Version 22.5.0: Mon Apr 24 20:52:24 - PDT 2023; root:xnu-8796.121.2~5/RELEASE_ARM64_T6000 arm64"}' - method: post - uri: https://api.openai.com/v1/fine-tunes - response: - body: - string: "{\n \"object\": \"fine-tune\",\n \"id\": \"ft-vDSkQPyS15HrnjmJ2Xwwb0wi\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": 5,\n \"prompt_loss_weight\": - 0.01,\n \"learning_rate_multiplier\": 0.05\n },\n \"organization_id\": - \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n \"model\": \"babbage\",\n \"training_files\": - [\n {\n \"object\": \"file\",\n \"id\": \"file-llDq0Q9la7EBTScAowIotxxc\",\n - \ \"purpose\": \"fine-tune\",\n \"filename\": \"yun_file.jsonl\",\n - \ \"bytes\": 147697,\n \"created_at\": 1684447571,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [],\n \"created_at\": 1685662822,\n \"updated_at\": - 1685662822,\n \"status\": \"pending\",\n \"fine_tuned_model\": null,\n \"events\": - [\n {\n \"object\": \"fine-tune-event\",\n \"level\": \"info\",\n - \ \"message\": \"Created fine-tune: ft-vDSkQPyS15HrnjmJ2Xwwb0wi\",\n \"created_at\": - 1685662822\n }\n ]\n}\n" - headers: - Access-Control-Allow-Origin: - - '*' - CF-Cache-Status: - - DYNAMIC - CF-RAY: - - 7d0b507e19f243d3-EWR - Connection: - - keep-alive - Content-Encoding: - - gzip - Content-Type: - - application/json - Date: - - Thu, 01 Jun 2023 23:42:40 GMT - Server: - - cloudflare - Transfer-Encoding: - - chunked - alt-svc: - - h3=":443"; ma=86400 - openai-processing-ms: - - '100' - openai-version: - - '2020-10-01' - strict-transport-security: - - max-age=15724800; includeSubDomains - x-request-id: - - f2d2e23e90f495781c3c406720deda6e - status: - code: 200 - message: OK - url: https://api.openai.com/v1/fine-tunes -version: 1 diff --git a/tests/contrib/openai/cassettes/v0/fine_tune_list.yaml b/tests/contrib/openai/cassettes/v0/fine_tune_list.yaml deleted file mode 100644 index 76066344166..00000000000 --- a/tests/contrib/openai/cassettes/v0/fine_tune_list.yaml +++ /dev/null @@ -1,167 +0,0 @@ -interactions: -- request: - body: null - headers: - Accept: - - '*/*' - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - User-Agent: - - OpenAI/v1 PythonBindings/0.27.7 - X-OpenAI-Client-User-Agent: - - '{"bindings_version": "0.27.7", "httplib": "requests", "lang": "python", "lang_version": - "3.10.5", "platform": "macOS-13.4-arm64-arm-64bit", "publisher": "openai", - "uname": "Darwin 22.5.0 Darwin Kernel Version 22.5.0: Mon Apr 24 20:52:24 - PDT 2023; root:xnu-8796.121.2~5/RELEASE_ARM64_T6000 arm64"}' - method: GET - uri: https://api.openai.com/v1/fine-tunes - response: - body: - string: !!binary | - H4sIAAAAAAAAA+yZ227iOhSG7+cpENeTke04B/cOKBQoLcdy6NZW5CQGUnK0HSiM5t23QtmUMp0y - Iyia0w1E9p9k+V9Zn5aTzx9yuXxkPzBH5i9yed8TMv8xG3OppPmL3D8fcrlc7vP694Vy7IVMkWnI - 1vL1pOeuJ6TCFtjQB/g2MXgpal6Hd1ePt4l+NXiWTpcx4zHlNBD5i+3lc7l8aLE4cqbZKP74PGxT - 6Uwt4a1Y/iKn787EPApiafmRENaCeZNpFh34BOCOxmeUh144sTiVzApSX3qx7zG+VqKN7ss2uIhP - aOitqPSi0HpaVMQnSrOLV464vDVFxSybJXf6OLmZFfv4eVVB5DI/k1OXPo9KTr313ceez8TW011f - v/LWf7Z111rPZ4obVdXlJZZXkM+SEmw72I6i7rz18oQ45XEk2OuZWiuyi4U0WEtmc2GNI25FMQup - Z8WcxZQz99ODiEL/5Wn2Uq4XoWnEMJD2Ys7hjErmWjRbBdQNgjWACH6hEZLKNLtAljmHCcHc/CsC - y2WSen4mDFPf3wq+bI7+3do7p77nPuVqa/DzLGci9eWJrFcfZ0EvluXb5AZ3GuWi2URkgIPgsPXK - Uxzi2ylwoiD2fOZaG+UnR8xfdx6aJkHwgPE6UDF4X+MPZDufxu4rQW0D3wlIpI7DmLsT0No5K3PO - tXar6mIslYxMbjS5mM2FggBSgQrx+kABqoKQArCCNUUl+Q87Zf2DBBvCRSDqRI9bfqfRrdlw2gn0 - dheciGDwlACD7wAwl8690PHeDWLUGSdLeF8tlmbXfam3rwJs1OFjcAzEsufiLWJBpJO3qsaEUCNo - D2m/Mq70jtSvG6u01prcgTIl9Qq9v7kyF+fFFdJVdNB2gvSzwmo/1/uw2g/qx2G1qaBdYG3+lblw - IpdtkIUVCBQEFA0pQDsGWYNuz055UEqmBdy/LTK2SKt2Olz9RdZpqqlSELjd4CQoxkA6oYa5P+z2 - m8m7IkvH2oHa0QEw9N8GWVI+lBzQgdwI7FZvZKvtakuOyvqZkWWo8KDthKjnRtaLXL+CrCwofC5k - QQUQRUPHIKsxKYilVmrflW1cW417owRDHakPfwqynJR77N2AdX0/je+uL5NZetNTqXo9NvqNZWVW - PQZYAZPcc4SVPRfft0003waYCrEJ93YzvzLA5rK9oKDT7coouUV3PiTgrjJr9s4LMFPDB11H+Mz8 - 2kv1V/x6CuoIfq3LaYdeG1xp630hzKCFj8LVsBtzc9K5n9RHxqRaLDhFvkwqsvq3wzpN7RTi1rRS - 65RG7bI6iM1GYjyyYp2Qnw5YSCW/DbA695du0Cn7FaNbSqpBoeiWzFm9XD03sMxDrhNogvMDayfV - rwGLQBOesuHaQxbSFXjUeywxSWs3l7CuD5Jompj8odzQWjeNn3JTiH65DqvcvHrQWquOKSK9xVf9 - QU881iJYPwZYX9/w/xohKsDG21WiQqQB4/dpptiSujEaDtIFYY5dnDe9Bh3al+dlk0aIdtB2A+Iz - wynLNf72bnAvqFN2U5oCTUWDiqYegyajTYaNTptU3X5Vr0/debtabXX9+ERoQn/25m/04A8DnWF5 - X/N8FNv+qDZuFgQ+Bk0ycG0LAgCsIJp7THxXP6Xqhm68/d5d1ZAO3uFr1X8AAAD//+ydW5OiyBLH - v8rGvrtNXShg31BUFFQUr7x0AKKgCMhN8NOfsHvOjs54mtlBOT2OLx1GX6LTysqfWVn/TP5f2NLY - mS23KYNIQynSZGm50Vv8vB5WfE0IEP6mqn5l4RmKpisG16W3vwfXpVG3BBdbo0gN4hrAZcC18Sx7 - Oz6sDI/uCetdaoYwPBB/8QTXLYJHESbaaGwGozr0/Q5ebSVVS6bQLw8u+iu4Qiu1wsj6sQMh4jAG - BXHEAsg9ThW+Dxc7fynPYT7m90lqmp2pb29BvWKAYRZxRQBjEcZM1QBjAYLsBwC7MKo8wP7+sl1r - b7u3Fll6aNrnVHsXPVCkDNX02S5wuKBj1pEkzBZOWE/1OWDZZ3HrNjE11qS1vZKSRl0cjiiu2zm2 - po1Nnpfhmp/EQRL/EMUgjdiCLIyhEYseR6yl+LgVKPx62jjk2SENZW+30MmsYohhmmOKlh3TlZfi - 33z9v9UP70bhu1S23u8OEV2yGK8Ntn1XVB2X5CnDC3M5TYxD2u3+VneHP/UhcbfMzesueaXXsjec - jft6mi3rFqK/DYyfzdxi/xVSP5fBAYYwoKC+zFE0RT9O5Yy3bAwHMyU2ZuuYWa8mm3l92uJb1cKP - A5iiitadQAZUTL9LZ39Pvzej4L0TuBR+ZSKsAbqGcQ1RZZjoyjPURra5E7VFZkWCsJ0fMrnDPav9 - t4ip7rROhNQcGlPQQs2WspY5bS4cSwnAbN3cnnbI62knvFL0K6Bfdr7nxH74GueBFb0sndXqxypt - BFPsx0pLDCDDUo8jttg09OHKyRu4szB92DfSTCLHxOCrpRzCVOGys/Q3Iti7Q+4bX38HuXej0A0h - t/ONk1O+bN/aaft+BdzbtQFkSwrG1Igb5DLoDvOFWW9lLbIY86PmfvgE3C3CyZSH43Dk7x2UbAWm - sVHQek/5wfBXAhxC3AM1HNWt5co5DOhhMzGPXcl0mY3S2tte1YAjVNGyn4SbVQPuwtdXAHdhVAWA - Y2oUrGFQBnD7cDXPJxNBiQyxye+V7TQ1lSzYPxWxtwinLVK38RwenCgD/WZXcBl+oVom91kAhxFb - 0KKEAaIh9Tg3DdjjVT0WpFV/rph8p2snRBhuqX3FNw2ILpB4nNadptjKCXfu7GuEo2mKq5Rw8PS1 - DOHGzQ50PWW14zVhvqfsuboEy73UfxLuFvGUhP2hvLDTFMH6EoRBg6iu3pkon4VwNGEL6uEYIMLQ - j9OEuWow86acjya78TabGwOpmfs6UUaVEw7jonVnKK56wp07+xrhGIqrlnA0qCG2DOF6axy7vVFf - 5EarzAD01IzGhuHL/45w8Jcl3D2HX3iWesjtfLcQHe8Q2o3W0mMBczBK8S2Og+jvlxdz6f31PgDj - L9PfvfBK5+XN+trS2vkfnkspzIGC4MIU4B6naUCT1XHQ08cjgeFE1wPoqA/iw67ipgFIM3RRuowh - x1Z+ML1w9hWonYwqUXm7nINxgS+uBrmSF6sOEmg/6lv77XC8wMk+6GW0bWxmT3zdoGBNz7Cu6MMJ - OxdmdnqAfrvBOMD9/PgCFP5UZbUPw+/c2Gvhh8nXROfsjZi6Z1quWxB+/7yNnwuvzm5vaBEgTBwo - ei9vqeN5ByiS9LuE133PP6g5jDJubbQ2ba3eI4upb1J+NxN+gQDjHqip0BYb+3wxYqPmUaYFpIKp - b7Zsa1R1fkBzxfkBrHhO1rfOvpofnBl1QwH8e4YAmJJNhXxfnLeFjThFsagnh2WvDdT1Xm/9Vggr - Wt9nCvHZU4iSYjdD5DZ0n3H7wgrO2YXt7127N62YcJhAqpBwTNWTak7/lGY+JtyZUTcnHCQ1VOoM - 1NVZrj0eNujeDPsbxBKibrPNPn0maTfRGeRTge+4E2gfg95gxchpLxtLTqnBNK7jxScL/1uEfn1j - wYeXbVRh5GDmgboSR/K64/SmCaE34rbVDZdp2mg3Z/2qc7LCzwmaZZjKeXXu6mu8OjfqtrwC1Clj - oEs17+zajd560dmoZrhEmTpxFK/fOxrrJ69uMvmPTJyJPOEGnVxoerHW3/DLtiXGN+DVh7J1mitS - O2GGg+hhCLXYSqa5z/0UN+azlCVMRjPzbF7x8D+aJUWr/t385SoIde7qa4RiOQjvRChw6h/BpaTp - umiTjk2lvSCVh/2Jq85ENNkmybO98DaxY7TjI9Ya1Eyot5g1w+6663aagHGp2Vn5j4/NQhRbdL9M - 04h5nA6baNpOtHjbXGkrIjQ7u9E6WHArp+rhyugHlp3QledTF76+QquTUex92gvpGkA1iiqZUfWO - /t7A4dhUTGvsunu833S0pPPbyJTuzSvJD6IO5aZA5TYaGrM+6UviVC2VU13A6vXN4A+LVgQVFq0I - hcnjFK3iwMyleryfCU3KhP4C2lOndYwrHk7KcSwoOnqTkySzamhdOPsKtAhAhLobtPCpJ5oupTyC - HuqHVujxNqPYwBtOvGxnDczwd4HWPa/uNaI0WqA74WxVPxrxFIZdEvgevBmwPjwKcnRhterUHfM4 - Z0HXW+5HyoxpNhLWZXn3KLXyQ9KcV11fh5AUrjtkcOWoOnf2NVSdG3VLhRGgTwO0UKnJf8KKTXNG - nqHDohFE/n6uacthYvWemCofN1w+FVcxIlh1o91SNXYIO3a861SDKfbby/RrOxfDx8moxK2g+21x - SrflsD8IoDX2V2mijyvHFGIK150AWD2mzpx9FVNnRt0YU4iqgVK9eHBm+U11IwlHZahniSVICwum - Q/vZqXKLyFnLcYfrOZbQlfltGuGuNW8yi41UBlQvk8gKoxfd8/S/Aj3JDCtcW+GLYEXb2A9evjzd - 8J83cto3KfVDZGOYQq4xFPs4kxV4BfrTpaczyjRpREN/uOk6A0XtVlyKx4VUY0jV80u/cfU1qjGE - JncqxZO3Z4WVml8aNW2ZTsS1tN4MFrgh2sqEQVTzt+kxvmf6JcxhR2/0A+C2Yt/VPG+8PKzlbTWn - RJYDxadEliaPg6kRxfuGkh7CrcjbbbbftPN2R6qzFTfXnRpRiiqJDMVQlQtNT96GH5DqZBS4S/7F - nG4Nyz2DVcONmai3JHF0HB1bg7BLL7KNLMXPEvxtgocy63zPsKdCnIkzjdHdceB2upFahlWGa9kf - iq4Kw4Ql8HEuCf2psl55LGWtmseZkIr8rJlTy5lT9eMMi1edA5UrGi5cfQ1OLFfmcFhQbudO06jK - jdvbuyibC1Sw3knTxAVmdoiSpmtpzwPiLWKnPcrbdd0MIlMVw3AC+hJcjltS9mmmURFIFx1POILw - 43T1cNu1w8WHQ5IYvYDbdXKrv7M7q6rHwiNCkcJ1p6sfR8URRH+kIT0ZVdm8PXKSQMCy46jwJlv6 - vOCog/ow87reMbRyGRqDT0m4s180XT2KnJVjvgdG4EdO7KTW69v3T+/tD/Dn+a/7uyCJv/z46599 - eXbgn3//EYeJ9csB1PfCI0VOajvK970uL4IN3xWXXhmARnvXqUX6LnCtl9Pr1/fX/0J1ARBBBbOW - IASEg9Vjs9Rqk4gBiaL4JFvEcp6lDeiNlxmr3XW1C5scEEHsDyz2nUtotxWxzIecP3V9fpLlbhDL - aex3uLGsVT0TsXD2BISIBVWLWC7d+f2n0KVRtyxOkrfhOaSkhGW0Tylz1jYbKU23hSU7NFMjZn33 - WZwsHzmT3Nv0nV77OG3MJ9xytvMiSh2rqCIJS+HdMIQYg091+P8w0M6NvRZo+GyoYNVTJgItUuOj - ttgNW6N6O2rGVLBr+Nrhtwqj/wAAAP//7J3ZkuLKEYbv5ynm0r7AUXupLtl3gYQQS4SjA60gtLAj - iPC7O5ixj+k+BJpjgbpheAIqssgvlZmVf97JjXxBOpYGPGVTVGZts+g37XHfr/YyciMMk93omeYW - rY3U4dMVXR4MbyZHI92tDo/IWmb9xEICPMnujGWuNfX+si9h6PxQtyvxsx8jQSRlfqnPHVldbPoj - GllStD3ohUqxR4uDF6ZusJy61OKiAKoyqzJrt20v4lVpVq7fDlNvZhQYV5MPSdBEVtGPyhEPXe+v - ARYK3Qy3eD9uyJptDybAH3Uzzk04kZKyPgwxyD43Ob/sC6zC8Cy43ZJVEOQAzJFUmYlcb42bvWrQ - NHft/CzvQhnmB+XQeLEqvd8cSi3QW/akVnugzxti1ymTTTA9+8G7swpSxpLzE0bE8zQnLZH3+p3Y - WURhO7KD5mLvILU1znhHGKREYJJEK8oozpxW57d9iVbnh7opreBpGgimUrBZ+wM9pAsKwroxPAqr - j8ONSVdfMwH8K5X736BwP85Let7TQmg3LMXCrDiW26VoMbx/4f4NXWOkgIQk1jwF/AxEprK3Fnlh - S3VXLaUAo7KFqLq36Gw4v3/p/rq9T7Vc8gv2Fg9UvbeLMp30d73iDJkSOYYjMpp22tIh46AjOABJ - f2XMYNarxT/c56WgwyCR7lO+h/DUMEapxubliBfqx/rYJri5C2pM3/iwSofr10fyDVxnlR9MTaM6 - IJpQ+1VvZcFDnpN1JnVHxChOkhpEGEkEPc33cZEXauEqT/ObfAk4XiEIjmavwesZo0qCNGnrHcIC - ocy/j9/d9iVUnR/qpt/H6CTxkU7l3nVHFSgcFXQrsKOF/XF3WCsb9FaVR/Q7g8qMqzIwrFhxO+Fg - 0Da7dDhrVA0lE1BRiFHSsBYiAPDnAZVxXE7Hft5iJackrGkVD0uNoTCqWSfy9KMAwQXDYyyyHpb/ - cdtXhuV/HIreBVQ4h3AOphqWH+dBazNCHUfr+esuXautVc3dddALVDdwHCargVuMCl5dbe2dSmOo - yl4TlDMBFccUJxa+CBBPtK870JRKo0HWkYaD3mTdrjkzw9h486yTP4AT82rCKQPZg0pc29j941Dw - LqAiOYxSjmuVFemw2W+D+srVQH/N5pKu7HaS/Xor/AiS3MypaZ0Z08eaJMquz2XfbxamGbwVfsNX - S45E0MRvCsQwe7CSoyrtRKPGkalipT8+UlwYyjM+mmZQcrxqb4RBYhfsZG/pgUqOh3ofd9aLcCbW - 2KxwZvY1UNmyVuYlRw4STcsZlDKPOuf3eTHqnB3qxiXHk5RUDqdaKBz5g1oNYVCor+mhcpRULdhi - P5o9ftwBzx93WnbVLullVy/uhzHowQGXJ8sqijOIO+Tqc4DTQpukMRUKJPZogceuRaw0UtaRttmq - Gox5IPp161c01lMHnqsGRxQmJkMnez9Srytfmk6Hc6LHRlsqKXSM9Gm1Te2MC8gI0o9/0wumJThz - kej393kh8JwOda/Aw06b7NNJRLNx3VnWWp35jtg7vzLSFias91aH1/j3TTKSwk7fUsOed1YxkOsr - uRAMuFs304Dqzz/4vwVmSS0WCiFlT1OJ2RVrs0XPtVq4XrdqdASCOanv11G2aOKJud0JXTRzLp1f - 9SUuSYyxO3GJ5zDPkVT6XvZwUpvIqk/KpBwM3R6pFuuTEDovLt2khtnM86KjNLS5M1BaCkAoiBUl - mH4alxCiz9PKIguSLxWNyppucd4A3mQ+rmyGC5o1l5Kez58WXmbPpfOrvsCld4e6MZek07wPTMWl - 6toHTWsR0+5qbm36miGWjcAg+a/YyUIPxyV4MPJxiZW8gqa60PcWlTl0/W2q10H/0VMNJuY/AtOc - rDbTwx9iqj9NvH77kdO9OdHqzYrMbWCHm58m2mzD832M15NtBAgXiek2+zj6+Mik8xpHfXo0QBSE - B7PTnhxKRyNq+1lXJTHkMOkhJENQwMxhd37bl2CHoEB3gx1mKbXBxt4sFo5Fhs60Ise0Mq1xVJLE - 1+yGPRzsjhNzctRluTXoc0st2EVoHYLaQE3XuP8DVt//Bv9+VcUwUQ4aMcKeSA5aDbtlRZ0uV1uK - hd2OjfnMKdn1jOcaEUssETJGMteDfn/Xl1DFCL1XvihyhOdwqu+yZnmJykV3V9l5zXiuj+zpwN7A - QH292U7vOE59Xu67SncQ6kO52W2sV2RRHsyOaUD1Tr/+ryhvCU5BsgMJ6UspFl51vPPDXnI8fnnL - fCYSLFHMbWdTK/JDxTRdZ+CvA15WLPj6AriFY8VFhOT+dlSYD3dHdYOcllo2q5Z9nzIMJJyJJPkP - zhmVnibkz4e1vrw0nM64MbXcI99VKiCOambG+QkTACQViSVAsx/U4vxM8/0Ce06HulPzCp2GH1IO - agWBJ9bBenFQytoxZJX9zGMjGzRvRKeTOW8HqLPLeRHqmQiV4GDi2rMkziUmfVZwZ+3GaNxVYBfp - 0ZDVV8u95/C2fny5z8t9voj7SIBe+zbmEhDss9zHijxxbEV4Gse72WS+WlmxKHQIernPy32+hPsQ - 8q6X9if3IQRQ8WnRBwK16Cx7O/dQNifbKNhzfTEbmdGN3Af/3qnlBs+hq/eUUm3ljMOuOT86G398 - 1qf8v4rLOzs3nfhX5fJowis9zOhHQb2HHlttHBxJawPP7o23diEi3iKyxrqSbYZJkuRUCUFMZJ1e - nvamXittvT/UDZZJWLZvb+zgbIOEdNo0SEjKR5LI69N86yi7Q1RuzTdLJ682tsbo8ELVC1WPgyoJ - ets9YVuPWyrA8oT098Gi7Wa8ZxCLhEoYoQiTrBWICUVX16G+P9S9UIVP77nTiRFr+kJe7v2dvQra - tuuo+rLXOXQPvReqXqj6MnkJRRCgq84Gwae1vELJW22LSs11HIda4bRUhPOgnP9ttg7c14N8v7QE - ivAnvFzQemY+2tejTRynmog4bH/+I6/2jX8huSeEU/48712YEvbFFJM2m9hKNbbt1l4jatXNuvkl - CZxgeAokILKW/qKAQ3Hlcd77Q92y+SVOI8OU5Oh/xb++ff/+z2//+vZvAAAA//8DADRWMCLH8gAA - headers: - CF-Cache-Status: - - DYNAMIC - CF-RAY: - - 7d0b00df4ece430d-EWR - Connection: - - keep-alive - Content-Encoding: - - gzip - Content-Type: - - application/json - Date: - - Thu, 01 Jun 2023 22:48:19 GMT - Server: - - cloudflare - Transfer-Encoding: - - chunked - access-control-allow-origin: - - '*' - alt-svc: - - h3=":443"; ma=86400 - openai-processing-ms: - - '666' - openai-version: - - '2020-10-01' - strict-transport-security: - - max-age=15724800; includeSubDomains - x-request-id: - - 711df8aa18913c9cf2577e6c41233980 - status: - code: 200 - message: OK -version: 1 diff --git a/tests/contrib/openai/cassettes/v0/fine_tune_list_async.yaml b/tests/contrib/openai/cassettes/v0/fine_tune_list_async.yaml deleted file mode 100644 index 5d75b6fbd3b..00000000000 --- a/tests/contrib/openai/cassettes/v0/fine_tune_list_async.yaml +++ /dev/null @@ -1,872 +0,0 @@ -interactions: -- request: - body: null - headers: - User-Agent: - - OpenAI/v1 PythonBindings/0.27.7 - X-OpenAI-Client-User-Agent: - - '{"bindings_version": "0.27.7", "httplib": "requests", "lang": "python", "lang_version": - "3.10.5", "platform": "macOS-13.4-arm64-arm-64bit", "publisher": "openai", - "uname": "Darwin 22.5.0 Darwin Kernel Version 22.5.0: Mon Apr 24 20:52:24 - PDT 2023; root:xnu-8796.121.2~5/RELEASE_ARM64_T6000 arm64"}' - method: get - uri: https://api.openai.com/v1/fine-tunes - response: - body: - string: "{\n \"object\": \"list\",\n \"data\": [\n {\n \"object\": - \"fine-tune\",\n \"id\": \"ft-ew476W4Nq7rCoOKnUGxNq6GW\",\n \"hyperparams\": - {\n \"n_epochs\": 4,\n \"batch_size\": 64,\n \"prompt_loss_weight\": - 0.01,\n \"learning_rate_multiplier\": 0.2\n },\n \"organization_id\": - \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n \"model\": \"ada\",\n \"training_files\": - [\n {\n \"object\": \"file\",\n \"id\": \"file-doH3yD4tG1rkqC1Qc4booSvP\",\n - \ \"purpose\": \"fine-tune\",\n \"filename\": \"kvs_for_openai_prepared.jsonl\",\n - \ \"bytes\": 5597725,\n \"created_at\": 1679450294,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"validation_files\": [],\n \"result_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-3xkmTptENqM4RLEB8O29W4mm\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 188921,\n \"created_at\": 1679460340,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1679450294,\n \"updated_at\": 1679460341,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"ada:ft-datadog:kvs-20230314-2023-03-22-04-45-39\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-X1wmsJ96pPlRLSIb1hRm6QS0\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"davinci\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-acfqy1ZHBCkKVt6QGm47J1xm\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"data.jsonl\",\n \"bytes\": - 1269,\n \"created_at\": 1681159225,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-6Rt6KLzuIPgU0Ea9JFaZMG8w\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 2632,\n \"created_at\": 1681159926,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1681159225,\n \"updated_at\": 1681159926,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"davinci:ft-datadog:datadog-vscode-2023-04-10-20-52-05\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-WSTburmCqhA4VNBeewuHbuXz\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"davinci\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-FAs4QLr9mBp0tcn54rlXSVOq\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"data.jsonl\",\n \"bytes\": - 1645,\n \"created_at\": 1681160076,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-ttjCc0R1r7mbPTYb3QHPtYE6\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 2731,\n \"created_at\": 1681160993,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1681160076,\n \"updated_at\": 1681160994,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"davinci:ft-datadog:datadog-vscode-2023-04-10-21-09-52\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-LgAsy5CQUEb4IzfTYq41623j\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-KZhpUKDqkuMT3a3Kf7VLyFkH\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"metrics_data_prepared.jsonl\",\n - \ \"bytes\": 585,\n \"created_at\": 1683148121,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"validation_files\": [],\n \"result_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-vtQwa0RSStoqN2Ul190UFkOT\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 854,\n \"created_at\": 1683148243,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1683148121,\n \"updated_at\": 1683148244,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"curie:ft-datadog-2023-05-03-21-10-42\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-XSpr8gRZgJY7gHBAcBryqFtH\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"davinci\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-ApPhFIRCYQE3Wp8Lq7xeBJ99\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"metrics_data_prepared.jsonl\",\n - \ \"bytes\": 585,\n \"created_at\": 1683148239,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"validation_files\": [],\n \"result_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-RZDdmRElF7SCqHmABdC8kJEH\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 858,\n \"created_at\": 1683149180,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1683148239,\n \"updated_at\": 1683149181,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"davinci:ft-datadog-2023-05-03-21-26-19\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-sguIMD1J6Wqohq8rjEL5PMLz\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.2\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-EOGj5PzR8so6PrzVWTsxIo1J\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"file\",\n \"bytes\": 93047,\n - \ \"created_at\": 1683312507,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-veyadp2XWuw9ecbBvOiLaXbD\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 5995,\n \"created_at\": 1683312714,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1683312546,\n \"updated_at\": 1683312714,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"curie:ft-datadog-2023-05-05-18-51-53\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-7Q9XLRQ9HdVH6JhdvQHHPSlp\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 2,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-YjlXm6e4tZIil2pblYIfOAs4\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"tmdb_1000_movies_prepared.jsonl\",\n - \ \"bytes\": 367679,\n \"created_at\": 1683526040,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"validation_files\": [],\n \"result_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-Z8WhLG0b6KQKsZLKdjaFAXBr\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 113426,\n \"created_at\": 1683527055,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1683526040,\n \"updated_at\": 1683527055,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"curie:ft-datadog-2023-05-08-06-24-14\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-jnehkTwfbn5MDgmvcr2rw6oY\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 2,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-PDUZRTcpRB2ooI4fkKSZuV2o\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"tmdb_1500_movies_reverse_prepared.jsonl\",\n - \ \"bytes\": 539441,\n \"created_at\": 1683581296,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"validation_files\": [],\n \"result_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-N2YmodLX2yTAquvccIVohk1B\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 148396,\n \"created_at\": 1683583447,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1683581328,\n \"updated_at\": 1683583447,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"curie:ft-datadog:reverse-movie-search-2023-05-08-22-04-06\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-aWmpi9pIcB3KDWYirBvaX188\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"davinci\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-TZKghfKuCBHQR09JIzFVCjyy\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"output_prepared.jsonl\",\n \"bytes\": - 25389,\n \"created_at\": 1683753834,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-Po4FpPAgVCwyxwvrLnmYa6WB\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 4597,\n \"created_at\": 1683754543,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1683753835,\n \"updated_at\": 1683754544,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"davinci:ft-datadog-2023-05-10-21-35-42\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-ZOkNlHSil6yv7ADXLvubwvJJ\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie:ft-datadog:reverse-movie-search-2023-05-08-22-04-06\",\n - \ \"training_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-PnJdAPMFhj9h4NavxdBe35sv\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"tmdb_1500_to_2000_movies_reverse_prepared.jsonl\",\n - \ \"bytes\": 176718,\n \"created_at\": 1683905057,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"validation_files\": [],\n \"result_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-Aeh42OWPtbWgt7gfUjXBVFAF\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 91400,\n \"created_at\": 1683906271,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1683905057,\n \"updated_at\": 1683906272,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"curie:ft-datadog:reverse-movie-search-v2-2023-05-12-15-44-30\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-lLW3G3hcmHZYxesDDkXwxLI9\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.2\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-JVB6DvcQbV1F3EFPgL9ZXDzq\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"hackadog_2023_05_15/monitor_types/diffs_prepared.jsonl\",\n - \ \"bytes\": 64082,\n \"created_at\": 1684127801,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"validation_files\": [],\n \"result_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-jCaQfiyC4IYco2NbvxK6zubA\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 3402,\n \"created_at\": 1684128532,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1684127801,\n \"updated_at\": 1684128533,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"curie:ft-datadog:mobile-monitor-type-2023-05-15-05-28-52\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-Ss9OyL1JQyYcBFxF6YTAREqQ\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.2\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-cLQTrRoqi3ukD7CjP3gq0opQ\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"hackadog_2023_05_15/monitor_types/diffs_prepared.jsonl\",\n - \ \"bytes\": 64082,\n \"created_at\": 1684133994,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"validation_files\": [],\n \"result_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-BedfiwO5QEuczJKcl7jPFqhn\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 3460,\n \"created_at\": 1684134162,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1684133994,\n \"updated_at\": 1684134162,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"curie:ft-datadog:mobile-monitor-type-2023-05-15-07-02-41\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-qrfXyUUDPsbHEAqPkVvcPxpq\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-k3SktX2wisx1NEJDl7AYSec9\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"hackadog_2023_05_15/monitor_types/diffs_prepared.jsonl\",\n - \ \"bytes\": 43869,\n \"created_at\": 1684135206,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"validation_files\": [],\n \"result_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-4nASatDKfNXPcAIJhu6DQk0q\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 13595,\n \"created_at\": 1684135508,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1684135206,\n \"updated_at\": 1684135509,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"curie:ft-datadog:mobile-monitor-type-2023-05-15-07-25-07\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-TEI2lnPfmAZDXq0hXSd1dqKN\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-urNQLYhvv32Bd1rpC6SlaIUP\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"hackadog_2023_05_15/monitor_types/diffs_prepared.jsonl\",\n - \ \"bytes\": 56897,\n \"created_at\": 1684136755,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"validation_files\": [],\n \"result_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-fC7XELyRUmTkxXbOKEyoa6PR\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 13544,\n \"created_at\": 1684137098,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1684136755,\n \"updated_at\": 1684137099,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"curie:ft-datadog:mobile-monitor-type-2023-05-15-07-51-38\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-Mg4tlMRNH9Rfxb15VcsTbboL\",\n - \ \"hyperparams\": {\n \"n_epochs\": 2,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"ada\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-neSwyhymYHinwrhCFdn817wb\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"https://cdn.openai.com/API/train-demo.jsonl\",\n - \ \"bytes\": 60491,\n \"created_at\": 1684140199,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"validation_files\": [],\n \"result_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-ZLSTpMaTRD79Hln13zaOtwmH\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 25759,\n \"created_at\": 1684142982,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1684140199,\n \"updated_at\": 1684142983,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"ada:ft-datadog-2023-05-15-09-29-42\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-i3D5osNeqkQTY4uqpMx5hbjW\",\n - \ \"hyperparams\": {\n \"n_epochs\": 2,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"ada\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-j5W4aPaQU8XDWhvw2oGC7i1l\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"https://cdn.openai.com/API/train-demo.jsonl\",\n - \ \"bytes\": 60491,\n \"created_at\": 1684141044,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"validation_files\": [],\n \"result_files\": [],\n \"created_at\": - 1684141044,\n \"updated_at\": 1684144698,\n \"status\": \"cancelled\",\n - \ \"fine_tuned_model\": null\n },\n {\n \"object\": \"fine-tune\",\n - \ \"id\": \"ft-ImqbZs167tpPaMyFSTXI1PKK\",\n \"hyperparams\": {\n - \ \"n_epochs\": 2,\n \"batch_size\": 1,\n \"prompt_loss_weight\": - 0.01,\n \"learning_rate_multiplier\": 0.1\n },\n \"organization_id\": - \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n \"model\": \"curie\",\n \"training_files\": - [\n {\n \"object\": \"file\",\n \"id\": \"file-3EQsx9gbFjGZBM6YVoc0oJxD\",\n - \ \"purpose\": \"fine-tune\",\n \"filename\": \"https://cdn.openai.com/API/train-demo.jsonl\",\n - \ \"bytes\": 60491,\n \"created_at\": 1684141939,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"validation_files\": [],\n \"result_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-hHCqyYR8sEzL5D3S1VocFheR\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 25599,\n \"created_at\": 1684142240,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1684141939,\n \"updated_at\": 1684142240,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"curie:ft-datadog-2023-05-15-09-17-19\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-ANHXGDjHV3tHauwdMG1SgqaF\",\n - \ \"hyperparams\": {\n \"n_epochs\": 2,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie:ft-datadog-2023-05-15-09-17-19\",\n \"training_files\": - [\n {\n \"object\": \"file\",\n \"id\": \"file-j5W4aPaQU8XDWhvw2oGC7i1l\",\n - \ \"purpose\": \"fine-tune\",\n \"filename\": \"https://cdn.openai.com/API/train-demo.jsonl\",\n - \ \"bytes\": 60491,\n \"created_at\": 1684141044,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"validation_files\": [],\n \"result_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-PnbH9j5N7lNDf2X8YhoqlhMV\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 24620,\n \"created_at\": 1684142793,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1684142579,\n \"updated_at\": 1684142793,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"curie:ft-datadog-2023-05-15-09-26-32\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-Ja89GTQC5MW4oj3866Skxjqv\",\n - \ \"hyperparams\": {\n \"n_epochs\": 2,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-cyVDAIlU2hzpMOf7LvMxTKiH\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"linting_prepared_valid.jsonl\",\n - \ \"bytes\": 400,\n \"created_at\": 1684144740,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"validation_files\": [],\n \"result_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-RLgIiMVu65jHkFJrdvvCGEWN\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 251,\n \"created_at\": 1684145877,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1684144740,\n \"updated_at\": 1684145877,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"curie:ft-datadog-2023-05-15-10-17-56\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-mGCMgYIjScrd3xSUiPnNMzbg\",\n - \ \"hyperparams\": {\n \"n_epochs\": 2,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-F6UiULU9OIyDEntZNjAdGeHt\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"linting.jsonl\",\n \"bytes\": - 1592,\n \"created_at\": 1684147923,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-YkKccqyov4CXWv867x57XxX6\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 586,\n \"created_at\": 1684148921,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1684147923,\n \"updated_at\": 1684148922,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"curie:ft-datadog-2023-05-15-11-08-40\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-aHh6Ih0vMpvLQNUlSWH3Ukuu\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"davinci\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-bGtz4ZC0WDBF7g78mJgGvu1T\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"my_data_prepared.jsonl\",\n \"bytes\": - 3084,\n \"created_at\": 1684155377,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-sVGuZtkEfZf6DEImRgpY9fiw\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 2384,\n \"created_at\": 1684155657,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1684155377,\n \"updated_at\": 1684155658,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"davinci:ft-datadog-2023-05-15-13-00-56\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-Mzoqb4rTcPceTllq4qjIZuIN\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"davinci\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-KopsI0lv1S9jZ3T8o6NKHVSt\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"data_prepared_train.jsonl\",\n \"bytes\": - 66391,\n \"created_at\": 1684160464,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-tpcyKBtqWDE0c2oY2hViFztT\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 99810,\n \"created_at\": 1684161359,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1684160464,\n \"updated_at\": 1684161360,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"davinci:ft-datadog-2023-05-15-14-35-58\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-2n3NrernAh7Ph1nQUnxmeOcr\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"ada\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-Z6PCF1JU9hSazbtV2rJ6pon2\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"data_prepared.jsonl\",\n \"bytes\": - 19500,\n \"created_at\": 1684164083,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-lndqRPW7ECu8l8AlzKFywuEX\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 24226,\n \"created_at\": 1684164274,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1684164083,\n \"updated_at\": 1684164274,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"ada:ft-datadog-2023-05-15-15-24-33\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-Df8vy7LW3wYCpsoqXZZdQueM\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"ada\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-9yVHft364SlsmdSbm34ihtmI\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"data_prepared.jsonl\",\n \"bytes\": - 18240,\n \"created_at\": 1684164424,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-HkDaoGHV5GLrNOp2eTofvuaT\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 24237,\n \"created_at\": 1684164612,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1684164424,\n \"updated_at\": 1684164612,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"ada:ft-datadog-2023-05-15-15-30-11\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-2WeoESjKDzPQaxueDKYe2vQh\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-gLtI9MieDJLAkvs4JeXE7YjK\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"/Users/anna.pauxberger/Desktop/openai_training_datav0_prepared.jsonl\",\n - \ \"bytes\": 770,\n \"created_at\": 1684167081,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"validation_files\": [],\n \"result_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-AP2oVdna7PVuCsQoQjJiOPSJ\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 547,\n \"created_at\": 1684167655,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1684167081,\n \"updated_at\": 1684167656,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"curie:ft-datadog-2023-05-15-16-20-54\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-sEhL5uHgKgjOY4CHhPU730Eq\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"ada\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-DX2IaCNp1lFtolZnnTdwgLk2\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"data_prepared.jsonl\",\n \"bytes\": - 89100,\n \"created_at\": 1684168561,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-R0AobPvwrkHAhG8NEhyGIKB8\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 125751,\n \"created_at\": 1684170700,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1684168562,\n \"updated_at\": 1684170701,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"ada:ft-datadog-2023-05-15-17-11-39\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-Z4CWHaFKHRzRzFOrJ5YxjLKt\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"davinci\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-0cBAMbhVDtxHWZ7alTplIJsS\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"bleh.jsonl\",\n \"bytes\": - 41,\n \"created_at\": 1684178627,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-oVPgfn80efEzWDvHAWEy0dWi\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 271,\n \"created_at\": 1684178911,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1684178627,\n \"updated_at\": 1684178912,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"davinci:ft-datadog-2023-05-15-19-28-30\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-ql3xXD0pgmKVul1cxwsuEleZ\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-GRyGBacpscSHrrU1NK2dTFKx\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"hackadog_2023_05_15/monitor_types/diffs_prepared.jsonl\",\n - \ \"bytes\": 66257,\n \"created_at\": 1684196349,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"validation_files\": [],\n \"result_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-9kgi9twwuubMp9mIyeNmhIfB\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 13606,\n \"created_at\": 1684196562,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1684196350,\n \"updated_at\": 1684196563,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"curie:ft-datadog:mobile-monitor-type-2023-05-16-00-22-41\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-4jxdoADiSOBQxnJnzreyL2bO\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1,\n \"classification_positive_class\": \" 1\",\n \"compute_classification_metrics\": - true\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-onrz06LQNU0oonJAH1jAJHdn\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"sqli-sample/sqli_sample_prepared_train.jsonl\",\n - \ \"bytes\": 13635,\n \"created_at\": 1684221692,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"validation_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-6s71uPPo6xYtLyxvC2nTdx8Z\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"sqli-sample/sqli_sample_prepared_valid.jsonl\",\n - \ \"bytes\": 3638,\n \"created_at\": 1684221695,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"result_files\": [\n {\n \"object\": \"file\",\n \"id\": - \"file-tXQ9oVloAUxylptLvtoI9TLZ\",\n \"purpose\": \"fine-tune-results\",\n - \ \"filename\": \"compiled_results.csv\",\n \"bytes\": 34759,\n - \ \"created_at\": 1684223819,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"created_at\": - 1684221695,\n \"updated_at\": 1684223819,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"curie:ft-datadog-2023-05-16-07-56-58\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-Rqv0cWGcCv55GDd8Qcvbt8ol\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"ada\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-UynjNiMGzVCXU9dWmns0STS3\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"data_prepared.jsonl\",\n \"bytes\": - 19240,\n \"created_at\": 1684224417,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [],\n \"created_at\": 1684224417,\n \"updated_at\": - 1684224595,\n \"status\": \"cancelled\",\n \"fine_tuned_model\": - null\n },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-pZsStzZYmQFRBGsEt0pmCoZw\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"ada\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-l94OdT0jQtCQiMcClKeZUlGS\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"data_prepared.jsonl\",\n \"bytes\": - 19310,\n \"created_at\": 1684224740,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-dt8O7hr5qybjiNoYVgGXz2dq\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 24807,\n \"created_at\": 1684226682,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1684224740,\n \"updated_at\": 1684226682,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"ada:ft-datadog-2023-05-16-08-44-41\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-VkfNRptUY5od8ouyVBFCS5CW\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"ada\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-EDL79B0GN6G6dvuMpxrDiEIS\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"data_prepared_comb.jsonl\",\n \"bytes\": - 38950,\n \"created_at\": 1684225491,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-oH06n9Vcnu3wZJNTeeWa0lYP\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 37488,\n \"created_at\": 1684231309,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1684225491,\n \"updated_at\": 1684231310,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"ada:ft-datadog-2023-05-16-10-01-48\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-NILZKSGmKcvMAiAg1N1AWEnb\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"ada\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-yDL0SqS8LMWVkJ9vOE4tmhda\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"data_prepared_comb.jsonl\",\n \"bytes\": - 156640,\n \"created_at\": 1684226497,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-d9AjUOxfponMoemKpwf2RLZF\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 154934,\n \"created_at\": 1684235653,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1684226497,\n \"updated_at\": 1684235653,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"ada:ft-datadog-2023-05-16-11-14-12\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-slWVn5p50nIbXz9dU3ntc5rw\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1,\n \"classification_positive_class\": \"1\",\n \"compute_classification_metrics\": - true\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-ZA8VAjTn1eJdQd36CZNMDopX\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"sqli-sample/sqli_sample_prepared_train_2.jsonl\",\n - \ \"bytes\": 91449,\n \"created_at\": 1684229197,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"validation_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-TojnLRgrLQB1oEd25Rwd5iXk\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"sqli-sample/sqli_sample_prepared_valid_2.jsonl\",\n - \ \"bytes\": 22164,\n \"created_at\": 1684229199,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"result_files\": [\n {\n \"object\": \"file\",\n \"id\": - \"file-eCN5aUvSCi2c84znY4YhOM8y\",\n \"purpose\": \"fine-tune-results\",\n - \ \"filename\": \"compiled_results.csv\",\n \"bytes\": 197009,\n - \ \"created_at\": 1684236147,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"created_at\": - 1684229199,\n \"updated_at\": 1684236148,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"curie:ft-datadog-2023-05-16-11-22-26\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-No7BIzIZe43KvmH6Vtl1G5Xs\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"ada\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-erAWhcbGW4T9RUGjrd1yA74s\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"data_prepared.jsonl\",\n \"bytes\": - 265320,\n \"created_at\": 1684232842,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-C7BHnrA5AtAD0fjBmmzcSJ7I\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 181582,\n \"created_at\": 1684239223,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1684232842,\n \"updated_at\": 1684239223,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"ada:ft-datadog-2023-05-16-12-13-42\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-ggYF19fR0PF1OTnUZPXHEb5W\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 2,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"ada\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-cxGN0bdxQgOnWWMcP5XiJGbQ\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"data_prepared.jsonl\",\n \"bytes\": - 513270,\n \"created_at\": 1684240072,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-bzqhZlAd6DfD9dhG3XDJX9bG\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 155083,\n \"created_at\": 1684243394,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1684240073,\n \"updated_at\": 1684243395,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"ada:ft-datadog-2023-05-16-13-23-13\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-ZA0LtY2OfTSlsP5sRLrHgvO2\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 2,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"ada\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-b6NRmgCoBjIRLwfFJXRNjK0E\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"data_prepared.jsonl\",\n \"bytes\": - 735334,\n \"created_at\": 1684240901,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-mTQFJJ4soT3mSasMHfibbtjk\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 190364,\n \"created_at\": 1684247560,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1684240901,\n \"updated_at\": 1684247561,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"ada:ft-datadog-2023-05-16-14-32-39\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-EQ8ytwumIrgT0Us6k8VQvv8e\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1,\n \"classification_positive_class\": \" 1\",\n \"compute_classification_metrics\": - true\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-c6fHTOi6VZT89Egl7NllKBhn\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"sqli-sample/sqli_sample_prepared_train_3.jsonl\",\n - \ \"bytes\": 94953,\n \"created_at\": 1684242636,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"validation_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-R8v9JH72cR3QUZz53BXNi7Yh\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"sqli-sample/sqli_sample_prepared_valid_3.jsonl\",\n - \ \"bytes\": 23040,\n \"created_at\": 1684242638,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"result_files\": [\n {\n \"object\": \"file\",\n \"id\": - \"file-yIU3Ospni9s3cF76cUT0Fu6L\",\n \"purpose\": \"fine-tune-results\",\n - \ \"filename\": \"compiled_results.csv\",\n \"bytes\": 197700,\n - \ \"created_at\": 1684247618,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"created_at\": - 1684242638,\n \"updated_at\": 1684247618,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"curie:ft-datadog-2023-05-16-14-33-37\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-olWHH230BIs5yFz8RTmu3loi\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1,\n \"classification_positive_class\": \" 0\",\n \"compute_classification_metrics\": - true\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-LeGeDVEgVCwXx0S1W7NaqG2x\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"sqli-sample/sqli_sample_prepared_train_4.jsonl\",\n - \ \"bytes\": 104915,\n \"created_at\": 1684250866,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"validation_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-eHo6DYQsoTtuRT1x7m9UIdX6\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"sqli-sample/sqli_sample_prepared_valid_4.jsonl\",\n - \ \"bytes\": 25134,\n \"created_at\": 1684250869,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"result_files\": [\n {\n \"object\": \"file\",\n \"id\": - \"file-ADhhXk4VxbM8DQ5Z2VhGM5eI\",\n \"purpose\": \"fine-tune-results\",\n - \ \"filename\": \"compiled_results.csv\",\n \"bytes\": 215866,\n - \ \"created_at\": 1684254357,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"created_at\": - 1684250869,\n \"updated_at\": 1684254358,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"curie:ft-datadog-2023-05-16-16-25-56\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-6ZIfqHLOkv4evlFYTpc1ISry\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-cBvVu5bekOrx0NIrNBmW7gIc\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"file\",\n \"bytes\": 602,\n - \ \"created_at\": 1684251156,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-vCHipSgdL3IIdH5Y0mk4Iwso\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 753,\n \"created_at\": 1684258665,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1684251156,\n \"updated_at\": 1684258666,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"curie:ft-datadog-2023-05-16-17-37-44\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-eXaHaNRl4E4EmXgS4GCIan1f\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-mKA7CfQJTkfWQLQ022mxQQmh\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"file\",\n \"bytes\": 602,\n - \ \"created_at\": 1684252252,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-4p4ADCbFs5u3Ab0jakZFtXp5\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 758,\n \"created_at\": 1684260495,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1684252252,\n \"updated_at\": 1684260495,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"curie:ft-datadog-2023-05-16-18-08-14\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-Gsl0Kdpx5PrkdtUTb9qJmb4A\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 2,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.2\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-1ybAxD6DjBTRg1ljpFk1glus\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"/Users/mac.mccarthy/Desktop/prompts_jsonl_for_documentation_tuning_prepared.jsonl\",\n - \ \"bytes\": 10204795,\n \"created_at\": 1684256310,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"validation_files\": [],\n \"result_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-jJzVhzb0omnycOMayDzboMlL\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 131719,\n \"created_at\": 1684262191,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1684256310,\n \"updated_at\": 1684262192,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"curie:ft-datadog-2023-05-16-18-36-30\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-Zjix9fd4XfhFNx5FhH72D89e\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-zacazVNNLWU7dRBeC1dymHWR\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"da_prepared (1).jsonl\",\n \"bytes\": - 4770,\n \"created_at\": 1684264681,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-RnPEQRhqru539eMxbkifDeIP\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 2634,\n \"created_at\": 1684266455,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1684264681,\n \"updated_at\": 1684266456,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"curie:ft-datadog-2023-05-16-19-47-34\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-KEq2ECgvFvjKxkVYehWet1mR\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"ada\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-fIkEUgQPWnVXNKPJsr4pEWiz\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"training_data_prepared_train.jsonl\",\n - \ \"bytes\": 197504,\n \"created_at\": 1684266989,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"validation_files\": [],\n \"result_files\": [],\n \"created_at\": - 1684266989,\n \"updated_at\": 1684267793,\n \"status\": \"cancelled\",\n - \ \"fine_tuned_model\": null\n },\n {\n \"object\": \"fine-tune\",\n - \ \"id\": \"ft-ox7eftHC7yFccgfWlsm7EQd1\",\n \"hyperparams\": {\n - \ \"n_epochs\": 4,\n \"batch_size\": 1,\n \"prompt_loss_weight\": - 0.01,\n \"learning_rate_multiplier\": 0.1\n },\n \"organization_id\": - \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n \"model\": \"curie\",\n \"training_files\": - [\n {\n \"object\": \"file\",\n \"id\": \"file-xC22NUuYBkXvzRt2fLREcGde\",\n - \ \"purpose\": \"fine-tune\",\n \"filename\": \"file\",\n - \ \"bytes\": 147697,\n \"created_at\": 1684277658,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"validation_files\": [],\n \"result_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-kXHUNqbfOZJhdgz7vFF0xoHc\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 169003,\n \"created_at\": 1684280547,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1684277751,\n \"updated_at\": 1684280548,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"curie:ft-datadog-2023-05-16-23-42-26\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-mmj9smspyQETzn6Fwij6Ye0K\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - null,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - null\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-xC22NUuYBkXvzRt2fLREcGde\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"file\",\n \"bytes\": 147697,\n - \ \"created_at\": 1684277658,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [],\n \"created_at\": 1684277798,\n \"updated_at\": - 1684277868,\n \"status\": \"cancelled\",\n \"fine_tuned_model\": - null\n },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-6MJYZPQ1P2VoX6Irqwjf7MVz\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - null,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - null\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-xC22NUuYBkXvzRt2fLREcGde\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"file\",\n \"bytes\": 147697,\n - \ \"created_at\": 1684277658,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [],\n \"created_at\": 1684278059,\n \"updated_at\": - 1684278096,\n \"status\": \"cancelled\",\n \"fine_tuned_model\": - null\n },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-doj9zLo3hxxviakrrdx9BO42\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - null,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - null\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-xC22NUuYBkXvzRt2fLREcGde\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"file\",\n \"bytes\": 147697,\n - \ \"created_at\": 1684277658,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [],\n \"created_at\": 1684440495,\n \"updated_at\": - 1684440598,\n \"status\": \"cancelled\",\n \"fine_tuned_model\": - null\n },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-10RCfqSvgyEcauomw7VpiYco\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 3,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-t3k1gVSQDHrfZnPckzftlZ4A\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"dave-hal.jsonl\",\n \"bytes\": - 356,\n \"created_at\": 1684365950,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-bJyf8TM0jeSZueBo4jpodZVQ\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 410,\n \"created_at\": 1684442697,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1684442489,\n \"updated_at\": 1684442697,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"curie:ft-datadog:deleteme-2023-05-18-20-44-56\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-2jU5ALzNgX2ELktqfARJubYy\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 3,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-t3k1gVSQDHrfZnPckzftlZ4A\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"dave-hal.jsonl\",\n \"bytes\": - 356,\n \"created_at\": 1684365950,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-81juw46uj7dR03Na4UwmpMg8\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 393,\n \"created_at\": 1684452342,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1684452081,\n \"updated_at\": 1684452342,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"curie:ft-datadog:deleteme-2023-05-18-23-25-41\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-TVpNqwlvermMegfRVqSOyPyS\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 3,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-t3k1gVSQDHrfZnPckzftlZ4A\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"dave-hal.jsonl\",\n \"bytes\": - 356,\n \"created_at\": 1684365950,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [],\n \"created_at\": 1684452102,\n \"updated_at\": - 1684452103,\n \"status\": \"cancelled\",\n \"fine_tuned_model\": - null\n },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-n8jruCQHgfff5dnhDC1kmEAw\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-llDq0Q9la7EBTScAowIotxxc\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"yun_file.jsonl\",\n \"bytes\": - 147697,\n \"created_at\": 1684447571,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-6QnU9h34M6aeQGxeeLwT4RGg\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 168937,\n \"created_at\": 1684508093,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n - \ \"created_at\": 1684507190,\n \"updated_at\": 1684508093,\n \"status\": - \"succeeded\",\n \"fine_tuned_model\": \"curie:ft-datadog-2023-05-19-14-54-52\"\n - \ }\n ]\n}\n" - headers: - Access-Control-Allow-Origin: - - '*' - CF-Cache-Status: - - DYNAMIC - CF-RAY: - - 7d0b02be5df443d9-EWR - Connection: - - keep-alive - Content-Encoding: - - gzip - Content-Type: - - application/json - Date: - - Thu, 01 Jun 2023 22:49:36 GMT - Server: - - cloudflare - Transfer-Encoding: - - chunked - alt-svc: - - h3=":443"; ma=86400 - openai-processing-ms: - - '437' - openai-version: - - '2020-10-01' - strict-transport-security: - - max-age=15724800; includeSubDomains - x-request-id: - - 5269b3bb48cc35866011bec3c0c73503 - status: - code: 200 - message: OK - url: https://api.openai.com/v1/fine-tunes -version: 1 diff --git a/tests/contrib/openai/cassettes/v0/fine_tune_list_events.yaml b/tests/contrib/openai/cassettes/v0/fine_tune_list_events.yaml deleted file mode 100644 index d6c15b3080f..00000000000 --- a/tests/contrib/openai/cassettes/v0/fine_tune_list_events.yaml +++ /dev/null @@ -1,61 +0,0 @@ -interactions: -- request: - body: null - headers: - Accept: - - '*/*' - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - User-Agent: - - OpenAI/v1 PythonBindings/0.27.7 - X-OpenAI-Client-User-Agent: - - '{"bindings_version": "0.27.7", "httplib": "requests", "lang": "python", "lang_version": - "3.10.5", "platform": "macOS-13.4-arm64-arm-64bit", "publisher": "openai", - "uname": "Darwin 22.5.0 Darwin Kernel Version 22.5.0: Mon Apr 24 20:52:24 - PDT 2023; root:xnu-8796.121.2~5/RELEASE_ARM64_T6000 arm64"}' - method: GET - uri: https://api.openai.com/v1/fine-tunes/ft-N6ggcFNqJNuREixR9ShDWzST/events?stream=False - response: - body: - string: !!binary | - H4sIAAAAAAAAA7SOuw6CMBSG9z7FSWdIvFGVFWVwYAATB2NMLQesqSVKIUbCu5tClDg5ufzDf8vX - EABanC4oDPWBKlka6lgv5YZTH/YEAKDp9KuZSY2uqTS6WKPuR11FYY3KNqTOisG+YlnyHG0Q3JEb - TOFz4UNm3IjluQij2yaq4rV8xMvkvNo9k+1wIfrdkVuAMVt4jE3no1kXt84fQMP3FATXApXC9AfN - xOtpCMCBtOQFAAD//wMAMcDxjl4BAAA= - headers: - CF-Cache-Status: - - DYNAMIC - CF-RAY: - - 7d0ba96d6cc18c96-EWR - Connection: - - keep-alive - Content-Encoding: - - gzip - Content-Type: - - application/json - Date: - - Fri, 02 Jun 2023 00:43:23 GMT - Server: - - cloudflare - Transfer-Encoding: - - chunked - access-control-allow-origin: - - '*' - alt-svc: - - h3=":443"; ma=86400 - openai-processing-ms: - - '33' - openai-version: - - '2020-10-01' - strict-transport-security: - - max-age=15724800; includeSubDomains - x-request-id: - - 8944f6870b04e85d45104ae30433e051 - status: - code: 200 - message: OK -version: 1 - - diff --git a/tests/contrib/openai/cassettes/v0/fine_tune_retrieve.yaml b/tests/contrib/openai/cassettes/v0/fine_tune_retrieve.yaml deleted file mode 100644 index c47c54673db..00000000000 --- a/tests/contrib/openai/cassettes/v0/fine_tune_retrieve.yaml +++ /dev/null @@ -1,64 +0,0 @@ -interactions: -- request: - body: null - headers: - Accept: - - '*/*' - Accept-Encoding: - - gzip, deflate - Connection: - - keep-alive - User-Agent: - - OpenAI/v1 PythonBindings/0.27.7 - X-OpenAI-Client-User-Agent: - - '{"bindings_version": "0.27.7", "httplib": "requests", "lang": "python", "lang_version": - "3.10.5", "platform": "macOS-13.4-arm64-arm-64bit", "publisher": "openai", - "uname": "Darwin 22.5.0 Darwin Kernel Version 22.5.0: Mon Apr 24 20:52:24 - PDT 2023; root:xnu-8796.121.2~5/RELEASE_ARM64_T6000 arm64"}' - method: GET - uri: https://api.openai.com/v1/fine-tunes/ft-sADEaavxRFrjOQ65XkQKm0zM - response: - body: - string: !!binary | - H4sIAAAAAAAAA7STUW+bMBSF3/kVFs+hIiuQhLe2SaRp6qqs1TRpmixjbsCpsZlt0iRV/vtkmwaN - berTXs85Nud+17wGCIWy2AE1YY7CLRMQmU5AOLEGK51oIn2zXBGyP3xZq93DJku/PW8+NfHp3sfq - YwuqJYo0OszRa4AQQqHA0EpaWyWZeKkghtZYsxOEOUp7sVWyaQ3mUmv8AqyqbZH4Kp72PgeiBBMV - VsQAbjpuWMsZKJ9KA4TOroRUFRHsRAyTAvviUlXRw2Nyonr5ea7X89X8rqwP1f3z7dfEN29kCdxG - C1IUpOrHNoow98kt42AH+O6q+MFGvLg/4/QeF+MQcb78GW8WnMxWt0+P9Ea+fJTmcKBDuO1UKzX8 - Sd259hJBGmcfO+GaXO20FHzIFEfj2k2TWbaYXWSqgBgoMbENp9k8SZJZOptefG2I6ew5i56C1lCG - IxOXYAjjNiQ6zp15DhD64fDsCWel53wB5B0FuuNmrI4LpVn2YZHFzuvacuxdx/G184ailAgKnPdF - HS9seZX4bYG2pvNgD8K8s7OeduSyw+wc9v4xMLGVg9yA1vZp5Ci885OgyxU5eu/n+OtKegKe6+Q/ - FF2/HUW/o/tXG8t82HJwDn4BAAD//wMAziFd4hgEAAA= - headers: - CF-Cache-Status: - - DYNAMIC - CF-RAY: - - 7d0b5491ae5dc45e-EWR - Connection: - - keep-alive - Content-Encoding: - - gzip - Content-Type: - - application/json - Date: - - Thu, 01 Jun 2023 23:45:27 GMT - Server: - - cloudflare - Transfer-Encoding: - - chunked - access-control-allow-origin: - - '*' - alt-svc: - - h3=":443"; ma=86400 - openai-processing-ms: - - '34' - openai-version: - - '2020-10-01' - strict-transport-security: - - max-age=15724800; includeSubDomains - x-request-id: - - fd45b5567a6c143fad37350f26250bc9 - status: - code: 200 - message: OK -version: 1 diff --git a/tests/contrib/openai/cassettes/v0/fine_tune_retrieve_async.yaml b/tests/contrib/openai/cassettes/v0/fine_tune_retrieve_async.yaml deleted file mode 100644 index c327bec4008..00000000000 --- a/tests/contrib/openai/cassettes/v0/fine_tune_retrieve_async.yaml +++ /dev/null @@ -1,64 +0,0 @@ -interactions: -- request: - body: null - headers: - User-Agent: - - OpenAI/v1 PythonBindings/0.27.7 - X-OpenAI-Client-User-Agent: - - '{"bindings_version": "0.27.7", "httplib": "requests", "lang": "python", "lang_version": - "3.10.5", "platform": "macOS-13.4-arm64-arm-64bit", "publisher": "openai", - "uname": "Darwin 22.5.0 Darwin Kernel Version 22.5.0: Mon Apr 24 20:52:24 - PDT 2023; root:xnu-8796.121.2~5/RELEASE_ARM64_T6000 arm64"}' - method: get - uri: https://api.openai.com/v1/fine-tunes/ft-sADEaavxRFrjOQ65XkQKm0zM - response: - body: - string: "{\n \"object\": \"fine-tune\",\n \"id\": \"ft-sADEaavxRFrjOQ65XkQKm0zM\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": 5,\n \"prompt_loss_weight\": - 0.01,\n \"learning_rate_multiplier\": 0.05\n },\n \"organization_id\": - \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n \"model\": \"babbage\",\n \"training_files\": - [\n {\n \"object\": \"file\",\n \"id\": \"file-llDq0Q9la7EBTScAowIotxxc\",\n - \ \"purpose\": \"fine-tune\",\n \"filename\": \"yun_file.jsonl\",\n - \ \"bytes\": 147697,\n \"created_at\": 1684447571,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [],\n \"created_at\": 1685662960,\n \"updated_at\": - 1685663003,\n \"status\": \"cancelled\",\n \"fine_tuned_model\": null,\n - \ \"events\": [\n {\n \"object\": \"fine-tune-event\",\n \"level\": - \"info\",\n \"message\": \"Created fine-tune: ft-sADEaavxRFrjOQ65XkQKm0zM\",\n - \ \"created_at\": 1685662960\n },\n {\n \"object\": \"fine-tune-event\",\n - \ \"level\": \"info\",\n \"message\": \"Fine-tune cancelled\",\n - \ \"created_at\": 1685663003\n }\n ]\n}\n" - headers: - Access-Control-Allow-Origin: - - '*' - CF-Cache-Status: - - DYNAMIC - CF-RAY: - - 7d0b60d7b8850fa7-EWR - Connection: - - keep-alive - Content-Encoding: - - gzip - Content-Type: - - application/json - Date: - - Thu, 01 Jun 2023 23:53:50 GMT - Server: - - cloudflare - Transfer-Encoding: - - chunked - alt-svc: - - h3=":443"; ma=86400 - openai-processing-ms: - - '39' - openai-version: - - '2020-10-01' - strict-transport-security: - - max-age=15724800; includeSubDomains - x-request-id: - - 15224a78b8d3e77af28ca490ff3cff5e - status: - code: 200 - message: OK - url: https://api.openai.com/v1/fine-tunes/ft-sADEaavxRFrjOQ65XkQKm0zM -version: 1 diff --git a/tests/contrib/openai/cassettes/v1/edit.yaml b/tests/contrib/openai/cassettes/v1/edit.yaml deleted file mode 100644 index 6dcc286533b..00000000000 --- a/tests/contrib/openai/cassettes/v1/edit.yaml +++ /dev/null @@ -1,84 +0,0 @@ -interactions: -- request: - body: '{"instruction": "fix spelling mistakes", "model": "text-davinci-edit-001", - "input": "thsi si a spelilgn imstkae.", "n": 3, "temperature": 0.2}' - headers: - accept: - - application/json - accept-encoding: - - gzip, deflate - connection: - - keep-alive - content-length: - - '142' - content-type: - - application/json - host: - - api.openai.com - user-agent: - - OpenAI/Python 1.0.0-beta.3 - x-stainless-arch: - - arm64 - x-stainless-lang: - - python - x-stainless-os: - - MacOS - x-stainless-package-version: - - 1.0.0-beta.3 - x-stainless-runtime: - - CPython - x-stainless-runtime-version: - - 3.10.5 - method: POST - uri: https://api.openai.com/v1/edits - response: - content: "{\n \"object\": \"edit\",\n \"created\": 1684789878,\n \"choices\": - [\n {\n \"text\": \"this is a spelling mistake.\\n\",\n \"index\": - 0\n },\n {\n \"text\": \"this is a spelling mistake.\\n\",\n \"index\": - 1\n },\n {\n \"text\": \"this is a spelling mistake.\\n\",\n \"index\": - 2\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 28,\n \"completion_tokens\": - 90,\n \"total_tokens\": 118\n }\n}\n" - headers: - CF-Cache-Status: - - DYNAMIC - CF-RAY: - - 81b49e22d9e0440e-EWR - Cache-Control: - - no-cache, must-revalidate - Connection: - - keep-alive - Content-Encoding: - - gzip - Content-Type: - - application/json - Date: - - Tue, 24 Oct 2023 19:27:33 GMT - Server: - - cloudflare - Transfer-Encoding: - - chunked - access-control-allow-origin: - - '*' - alt-svc: - - h3=":443"; ma=86400 - openai-model: - - text-davinci-edit:001 - openai-organization: - - datadog-4 - openai-processing-ms: - - '696' - openai-version: - - '2020-10-01' - strict-transport-security: - - max-age=15724800; includeSubDomains - x-ratelimit-limit-requests: - - '20' - x-ratelimit-remaining-requests: - - '19' - x-ratelimit-reset-requests: - - 3s - x-request-id: - - a63741b463117a416982b86031fc189b - http_version: HTTP/1.1 - status_code: 200 -version: 1 diff --git a/tests/contrib/openai/cassettes/v1/edit_async.yaml b/tests/contrib/openai/cassettes/v1/edit_async.yaml deleted file mode 100644 index 61bf61f9b7d..00000000000 --- a/tests/contrib/openai/cassettes/v1/edit_async.yaml +++ /dev/null @@ -1,84 +0,0 @@ -interactions: -- request: - body: '{"instruction": "fix spelling mistakes", "model": "text-davinci-edit-001", - "input": "thsi si a spelilgn imstkae.", "n": 3, "top_p": 0.3}' - headers: - accept: - - application/json - accept-encoding: - - gzip, deflate - connection: - - keep-alive - content-length: - - '136' - content-type: - - application/json - host: - - api.openai.com - user-agent: - - AsyncOpenAI/Python 1.0.0-beta.3 - x-stainless-arch: - - arm64 - x-stainless-lang: - - python - x-stainless-os: - - MacOS - x-stainless-package-version: - - 1.0.0-beta.3 - x-stainless-runtime: - - CPython - x-stainless-runtime-version: - - 3.10.5 - method: POST - uri: https://api.openai.com/v1/edits - response: - content: "{\n \"object\": \"edit\",\n \"created\": 1684789878,\n \"choices\": - [\n {\n \"text\": \"this is a spelling mistake.\\n\",\n \"index\": - 0\n },\n {\n \"text\": \"this is a spelling mistake.\\n\",\n \"index\": - 1\n },\n {\n \"text\": \"this is a spelling mistake.\\n\",\n \"index\": - 2\n }\n ],\n \"usage\": {\n \"prompt_tokens\": 28,\n \"completion_tokens\": - 90,\n \"total_tokens\": 118\n }\n}\n" - headers: - CF-Cache-Status: - - DYNAMIC - CF-RAY: - - 81b4a65018fa41cd-EWR - Cache-Control: - - no-cache, must-revalidate - Connection: - - keep-alive - Content-Encoding: - - gzip - Content-Type: - - application/json - Date: - - Tue, 24 Oct 2023 19:33:07 GMT - Server: - - cloudflare - Transfer-Encoding: - - chunked - access-control-allow-origin: - - '*' - alt-svc: - - h3=":443"; ma=86400 - openai-model: - - text-davinci-edit:001 - openai-organization: - - datadog-4 - openai-processing-ms: - - '556' - openai-version: - - '2020-10-01' - strict-transport-security: - - max-age=15724800; includeSubDomains - x-ratelimit-limit-requests: - - '20' - x-ratelimit-remaining-requests: - - '19' - x-ratelimit-reset-requests: - - 3s - x-request-id: - - 655ef98a29ad10ec97536b7d072ed590 - http_version: HTTP/1.1 - status_code: 200 -version: 1 diff --git a/tests/contrib/openai/cassettes/v1/fine_tune_cancel.yaml b/tests/contrib/openai/cassettes/v1/fine_tune_cancel.yaml deleted file mode 100644 index 9182eff76a1..00000000000 --- a/tests/contrib/openai/cassettes/v1/fine_tune_cancel.yaml +++ /dev/null @@ -1,80 +0,0 @@ -interactions: -- request: - body: '' - headers: - accept: - - application/json - accept-encoding: - - gzip, deflate - connection: - - keep-alive - content-length: - - '0' - content-type: - - application/json - host: - - api.openai.com - user-agent: - - OpenAI/Python 1.0.0-beta.3 - x-stainless-arch: - - arm64 - x-stainless-lang: - - python - x-stainless-os: - - MacOS - x-stainless-package-version: - - 1.0.0-beta.3 - x-stainless-runtime: - - CPython - x-stainless-runtime-version: - - 3.10.5 - method: POST - uri: https://api.openai.com/v1/fine-tunes/ft-N6ggcFNqJNuREixR9ShDWzST/cancel - response: - content: "{\n \"object\": \"fine-tune\",\n \"id\": \"ft-N6ggcFNqJNuREixR9ShDWzST\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": 5,\n \"prompt_loss_weight\": - 0.01,\n \"learning_rate_multiplier\": 0.05\n },\n \"organization_id\": - \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n \"model\": \"curie\",\n \"training_files\": - [\n {\n \"object\": \"file\",\n \"id\": \"file-llDq0Q9la7EBTScAowIotxxc\",\n - \ \"purpose\": \"fine-tune\",\n \"filename\": \"yun_file.jsonl\",\n - \ \"bytes\": 147697,\n \"created_at\": 1684447571,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [],\n \"created_at\": 1685663704,\n \"updated_at\": - 1685663725,\n \"status\": \"cancelled\",\n \"fine_tuned_model\": null,\n \"events\": - [\n {\n \"object\": \"fine-tune-event\",\n \"level\": \"info\",\n - \ \"message\": \"Created fine-tune: ft-N6ggcFNqJNuREixR9ShDWzST\",\n \"created_at\": - 1698359842\n },\n {\n \"object\": \"fine-tune-event\",\n \"level\": - \"info\",\n \"message\": \"Fine-tune cancelled\",\n \"created_at\": - 1698359865\n }\n ]\n}\n" - headers: - CF-Cache-Status: - - DYNAMIC - CF-RAY: - - 81c62f86680b434a-EWR - Connection: - - keep-alive - Content-Encoding: - - gzip - Content-Type: - - application/json - Date: - - Thu, 26 Oct 2023 22:37:45 GMT - Server: - - cloudflare - Transfer-Encoding: - - chunked - access-control-allow-origin: - - '*' - alt-svc: - - h3=":443"; ma=86400 - openai-processing-ms: - - '77' - openai-version: - - '2020-10-01' - strict-transport-security: - - max-age=15724800; includeSubDomains - x-request-id: - - 4e14d85cee2e2494debda41b84ad85f7 - http_version: HTTP/1.1 - status_code: 200 -version: 1 diff --git a/tests/contrib/openai/cassettes/v1/fine_tune_create.yaml b/tests/contrib/openai/cassettes/v1/fine_tune_create.yaml deleted file mode 100644 index 3092219c04f..00000000000 --- a/tests/contrib/openai/cassettes/v1/fine_tune_create.yaml +++ /dev/null @@ -1,80 +0,0 @@ -interactions: -- request: - body: '{"training_file": "file-llDq0Q9la7EBTScAowIotxxc", "batch_size": 5, "compute_classification_metrics": - false, "learning_rate_multiplier": 0.05, "model": "babbage", "prompt_loss_weight": - 0.01, "suffix": "dummy-fine-tune-model"}' - headers: - accept: - - application/json - accept-encoding: - - gzip, deflate - connection: - - keep-alive - content-length: - - '225' - content-type: - - application/json - host: - - api.openai.com - user-agent: - - OpenAI/Python 1.0.0-beta.3 - x-stainless-arch: - - arm64 - x-stainless-lang: - - python - x-stainless-os: - - MacOS - x-stainless-package-version: - - 1.0.0-beta.3 - x-stainless-runtime: - - CPython - x-stainless-runtime-version: - - 3.10.5 - method: POST - uri: https://api.openai.com/v1/fine-tunes - response: - content: "{\n \"object\": \"fine-tune\",\n \"id\": \"ft-vDSkQPyS15HrnjmJ2Xwwb0wi\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": 5,\n \"prompt_loss_weight\": - 0.01,\n \"learning_rate_multiplier\": 0.05\n },\n \"organization_id\": - \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n \"model\": \"babbage\",\n \"training_files\": - [\n {\n \"object\": \"file\",\n \"id\": \"file-llDq0Q9la7EBTScAowIotxxc\",\n - \ \"purpose\": \"fine-tune\",\n \"filename\": \"yun_file.jsonl\",\n - \ \"bytes\": 147697,\n \"created_at\": 1685662822,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [],\n \"created_at\": 1685662822,\n \"updated_at\": - 1685662822,\n \"status\": \"pending\",\n \"fine_tuned_model\": null,\n \"events\": - [\n {\n \"object\": \"fine-tune-event\",\n \"level\": \"info\",\n - \ \"message\": \"Created fine-tune: ft-uIPtHCyzhjKIgvIZsM5Teb3V\",\n \"created_at\": - 1685662822\n }\n ]\n}\n" - headers: - CF-Cache-Status: - - DYNAMIC - CF-RAY: - - 81c4d5dcbbcb41f5-EWR - Connection: - - keep-alive - Content-Encoding: - - gzip - Content-Type: - - application/json - Date: - - Thu, 26 Oct 2023 18:41:47 GMT - Server: - - cloudflare - Transfer-Encoding: - - chunked - access-control-allow-origin: - - '*' - alt-svc: - - h3=":443"; ma=86400 - openai-processing-ms: - - '192' - openai-version: - - '2020-10-01' - strict-transport-security: - - max-age=15724800; includeSubDomains - x-request-id: - - d0e345fb0b71e3d60a6f918bbf54a1ca - http_version: HTTP/1.1 - status_code: 200 -version: 1 diff --git a/tests/contrib/openai/cassettes/v1/fine_tune_list.yaml b/tests/contrib/openai/cassettes/v1/fine_tune_list.yaml deleted file mode 100644 index 96de1bb4352..00000000000 --- a/tests/contrib/openai/cassettes/v1/fine_tune_list.yaml +++ /dev/null @@ -1,878 +0,0 @@ -interactions: -- request: - body: '' - headers: - accept: - - application/json - accept-encoding: - - gzip, deflate - connection: - - keep-alive - content-type: - - application/json - host: - - api.openai.com - user-agent: - - OpenAI/Python 1.0.0-beta.3 - x-stainless-arch: - - arm64 - x-stainless-lang: - - python - x-stainless-os: - - MacOS - x-stainless-package-version: - - 1.0.0-beta.3 - x-stainless-runtime: - - CPython - x-stainless-runtime-version: - - 3.10.5 - method: GET - uri: https://api.openai.com/v1/fine-tunes - response: - content: "{\n \"object\": \"list\",\n \"data\": [\n {\n \"object\": - \"fine-tune\",\n \"id\": \"ft-ew476W4Nq7rCoOKnUGxNq6GW\",\n \"hyperparams\": - {\n \"n_epochs\": 4,\n \"batch_size\": 64,\n \"prompt_loss_weight\": - 0.01,\n \"learning_rate_multiplier\": 0.2\n },\n \"organization_id\": - \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n \"model\": \"ada\",\n \"training_files\": - [\n {\n \"object\": \"file\",\n \"id\": \"file-doH3yD4tG1rkqC1Qc4booSvP\",\n - \ \"purpose\": \"fine-tune\",\n \"filename\": \"kvs_for_openai_prepared.jsonl\",\n - \ \"bytes\": 5597725,\n \"created_at\": 1679450294,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-3xkmTptENqM4RLEB8O29W4mm\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 188921,\n \"created_at\": 1679460340,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1679450294,\n \"updated_at\": 1679460341,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"ada:ft-datadog:kvs-20230314-2023-03-22-04-45-39\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-X1wmsJ96pPlRLSIb1hRm6QS0\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"davinci\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-acfqy1ZHBCkKVt6QGm47J1xm\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"data.jsonl\",\n \"bytes\": - 1269,\n \"created_at\": 1681159225,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-6Rt6KLzuIPgU0Ea9JFaZMG8w\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 2632,\n \"created_at\": 1681159926,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1681159225,\n \"updated_at\": 1681159926,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"davinci:ft-datadog:datadog-vscode-2023-04-10-20-52-05\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-WSTburmCqhA4VNBeewuHbuXz\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"davinci\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-FAs4QLr9mBp0tcn54rlXSVOq\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"data.jsonl\",\n \"bytes\": - 1645,\n \"created_at\": 1681160076,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-ttjCc0R1r7mbPTYb3QHPtYE6\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 2731,\n \"created_at\": 1681160993,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1681160076,\n \"updated_at\": 1681160994,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"davinci:ft-datadog:datadog-vscode-2023-04-10-21-09-52\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-LgAsy5CQUEb4IzfTYq41623j\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-KZhpUKDqkuMT3a3Kf7VLyFkH\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"metrics_data_prepared.jsonl\",\n \"bytes\": - 585,\n \"created_at\": 1683148121,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-vtQwa0RSStoqN2Ul190UFkOT\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 854,\n \"created_at\": 1683148243,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1683148121,\n \"updated_at\": 1683148244,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"curie:ft-datadog-2023-05-03-21-10-42\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-XSpr8gRZgJY7gHBAcBryqFtH\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"davinci\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-ApPhFIRCYQE3Wp8Lq7xeBJ99\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"metrics_data_prepared.jsonl\",\n \"bytes\": - 585,\n \"created_at\": 1683148239,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-RZDdmRElF7SCqHmABdC8kJEH\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 858,\n \"created_at\": 1683149180,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1683148239,\n \"updated_at\": 1683149181,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"davinci:ft-datadog-2023-05-03-21-26-19\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-sguIMD1J6Wqohq8rjEL5PMLz\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.2\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-EOGj5PzR8so6PrzVWTsxIo1J\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"file\",\n \"bytes\": 93047,\n - \ \"created_at\": 1683312507,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-veyadp2XWuw9ecbBvOiLaXbD\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 5995,\n \"created_at\": 1683312714,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1683312546,\n \"updated_at\": 1683312714,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"curie:ft-datadog-2023-05-05-18-51-53\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-7Q9XLRQ9HdVH6JhdvQHHPSlp\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 2,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-YjlXm6e4tZIil2pblYIfOAs4\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"tmdb_1000_movies_prepared.jsonl\",\n - \ \"bytes\": 367679,\n \"created_at\": 1683526040,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-Z8WhLG0b6KQKsZLKdjaFAXBr\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 113426,\n \"created_at\": 1683527055,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1683526040,\n \"updated_at\": 1683527055,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"curie:ft-datadog-2023-05-08-06-24-14\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-jnehkTwfbn5MDgmvcr2rw6oY\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 2,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-PDUZRTcpRB2ooI4fkKSZuV2o\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"tmdb_1500_movies_reverse_prepared.jsonl\",\n - \ \"bytes\": 539441,\n \"created_at\": 1683581296,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-N2YmodLX2yTAquvccIVohk1B\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 148396,\n \"created_at\": 1683583447,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1683581328,\n \"updated_at\": 1683583447,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"curie:ft-datadog:reverse-movie-search-2023-05-08-22-04-06\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-aWmpi9pIcB3KDWYirBvaX188\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"davinci\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-TZKghfKuCBHQR09JIzFVCjyy\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"output_prepared.jsonl\",\n \"bytes\": - 25389,\n \"created_at\": 1683753834,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-Po4FpPAgVCwyxwvrLnmYa6WB\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 4597,\n \"created_at\": 1683754543,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1683753835,\n \"updated_at\": 1683754544,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"davinci:ft-datadog-2023-05-10-21-35-42\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-ZOkNlHSil6yv7ADXLvubwvJJ\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie:ft-datadog:reverse-movie-search-2023-05-08-22-04-06\",\n - \ \"training_files\": [\n {\n \"object\": \"file\",\n \"id\": - \"file-PnJdAPMFhj9h4NavxdBe35sv\",\n \"purpose\": \"fine-tune\",\n - \ \"filename\": \"tmdb_1500_to_2000_movies_reverse_prepared.jsonl\",\n - \ \"bytes\": 176718,\n \"created_at\": 1683905057,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-Aeh42OWPtbWgt7gfUjXBVFAF\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 91400,\n \"created_at\": 1683906271,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1683905057,\n \"updated_at\": 1683906272,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"curie:ft-datadog:reverse-movie-search-v2-2023-05-12-15-44-30\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-lLW3G3hcmHZYxesDDkXwxLI9\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.2\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-JVB6DvcQbV1F3EFPgL9ZXDzq\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"hackadog_2023_05_15/monitor_types/diffs_prepared.jsonl\",\n - \ \"bytes\": 64082,\n \"created_at\": 1684127801,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-jCaQfiyC4IYco2NbvxK6zubA\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 3402,\n \"created_at\": 1684128532,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1684127801,\n \"updated_at\": 1684128533,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"curie:ft-datadog:mobile-monitor-type-2023-05-15-05-28-52\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-Ss9OyL1JQyYcBFxF6YTAREqQ\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.2\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-cLQTrRoqi3ukD7CjP3gq0opQ\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"hackadog_2023_05_15/monitor_types/diffs_prepared.jsonl\",\n - \ \"bytes\": 64082,\n \"created_at\": 1684133994,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-BedfiwO5QEuczJKcl7jPFqhn\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 3460,\n \"created_at\": 1684134162,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1684133994,\n \"updated_at\": 1684134162,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"curie:ft-datadog:mobile-monitor-type-2023-05-15-07-02-41\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-qrfXyUUDPsbHEAqPkVvcPxpq\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-k3SktX2wisx1NEJDl7AYSec9\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"hackadog_2023_05_15/monitor_types/diffs_prepared.jsonl\",\n - \ \"bytes\": 43869,\n \"created_at\": 1684135206,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-4nASatDKfNXPcAIJhu6DQk0q\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 13595,\n \"created_at\": 1684135508,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1684135206,\n \"updated_at\": 1684135509,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"curie:ft-datadog:mobile-monitor-type-2023-05-15-07-25-07\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-TEI2lnPfmAZDXq0hXSd1dqKN\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-urNQLYhvv32Bd1rpC6SlaIUP\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"hackadog_2023_05_15/monitor_types/diffs_prepared.jsonl\",\n - \ \"bytes\": 56897,\n \"created_at\": 1684136755,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-fC7XELyRUmTkxXbOKEyoa6PR\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 13544,\n \"created_at\": 1684137098,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1684136755,\n \"updated_at\": 1684137099,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"curie:ft-datadog:mobile-monitor-type-2023-05-15-07-51-38\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-Mg4tlMRNH9Rfxb15VcsTbboL\",\n - \ \"hyperparams\": {\n \"n_epochs\": 2,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"ada\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-neSwyhymYHinwrhCFdn817wb\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"https://cdn.openai.com/API/train-demo.jsonl\",\n - \ \"bytes\": 60491,\n \"created_at\": 1684140199,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-ZLSTpMaTRD79Hln13zaOtwmH\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 25759,\n \"created_at\": 1684142982,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1684140199,\n \"updated_at\": 1684142983,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"ada:ft-datadog-2023-05-15-09-29-42\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-i3D5osNeqkQTY4uqpMx5hbjW\",\n - \ \"hyperparams\": {\n \"n_epochs\": 2,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"ada\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-j5W4aPaQU8XDWhvw2oGC7i1l\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"https://cdn.openai.com/API/train-demo.jsonl\",\n - \ \"bytes\": 60491,\n \"created_at\": 1684141044,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [],\n \"created_at\": 1684141044,\n \"updated_at\": - 1684144698,\n \"status\": \"cancelled\",\n \"fine_tuned_model\": null\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-ImqbZs167tpPaMyFSTXI1PKK\",\n - \ \"hyperparams\": {\n \"n_epochs\": 2,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-3EQsx9gbFjGZBM6YVoc0oJxD\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"https://cdn.openai.com/API/train-demo.jsonl\",\n - \ \"bytes\": 60491,\n \"created_at\": 1684141939,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-hHCqyYR8sEzL5D3S1VocFheR\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 25599,\n \"created_at\": 1684142240,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1684141939,\n \"updated_at\": 1684142240,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"curie:ft-datadog-2023-05-15-09-17-19\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-ANHXGDjHV3tHauwdMG1SgqaF\",\n - \ \"hyperparams\": {\n \"n_epochs\": 2,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie:ft-datadog-2023-05-15-09-17-19\",\n \"training_files\": - [\n {\n \"object\": \"file\",\n \"id\": \"file-j5W4aPaQU8XDWhvw2oGC7i1l\",\n - \ \"purpose\": \"fine-tune\",\n \"filename\": \"https://cdn.openai.com/API/train-demo.jsonl\",\n - \ \"bytes\": 60491,\n \"created_at\": 1684141044,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-PnbH9j5N7lNDf2X8YhoqlhMV\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 24620,\n \"created_at\": 1684142793,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1684142579,\n \"updated_at\": 1684142793,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"curie:ft-datadog-2023-05-15-09-26-32\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-Ja89GTQC5MW4oj3866Skxjqv\",\n - \ \"hyperparams\": {\n \"n_epochs\": 2,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-cyVDAIlU2hzpMOf7LvMxTKiH\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"linting_prepared_valid.jsonl\",\n - \ \"bytes\": 400,\n \"created_at\": 1684144740,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-RLgIiMVu65jHkFJrdvvCGEWN\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 251,\n \"created_at\": 1684145877,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1684144740,\n \"updated_at\": 1684145877,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"curie:ft-datadog-2023-05-15-10-17-56\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-mGCMgYIjScrd3xSUiPnNMzbg\",\n - \ \"hyperparams\": {\n \"n_epochs\": 2,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-F6UiULU9OIyDEntZNjAdGeHt\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"linting.jsonl\",\n \"bytes\": - 1592,\n \"created_at\": 1684147923,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-YkKccqyov4CXWv867x57XxX6\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 586,\n \"created_at\": 1684148921,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1684147923,\n \"updated_at\": 1684148922,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"curie:ft-datadog-2023-05-15-11-08-40\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-aHh6Ih0vMpvLQNUlSWH3Ukuu\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"davinci\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-bGtz4ZC0WDBF7g78mJgGvu1T\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"my_data_prepared.jsonl\",\n \"bytes\": - 3084,\n \"created_at\": 1684155377,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-sVGuZtkEfZf6DEImRgpY9fiw\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 2384,\n \"created_at\": 1684155657,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1684155377,\n \"updated_at\": 1684155658,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"davinci:ft-datadog-2023-05-15-13-00-56\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-Mzoqb4rTcPceTllq4qjIZuIN\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"davinci\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-KopsI0lv1S9jZ3T8o6NKHVSt\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"data_prepared_train.jsonl\",\n \"bytes\": - 66391,\n \"created_at\": 1684160464,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-tpcyKBtqWDE0c2oY2hViFztT\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 99810,\n \"created_at\": 1684161359,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1684160464,\n \"updated_at\": 1684161360,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"davinci:ft-datadog-2023-05-15-14-35-58\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-2n3NrernAh7Ph1nQUnxmeOcr\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"ada\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-Z6PCF1JU9hSazbtV2rJ6pon2\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"data_prepared.jsonl\",\n \"bytes\": - 19500,\n \"created_at\": 1684164083,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-lndqRPW7ECu8l8AlzKFywuEX\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 24226,\n \"created_at\": 1684164274,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1684164083,\n \"updated_at\": 1684164274,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"ada:ft-datadog-2023-05-15-15-24-33\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-Df8vy7LW3wYCpsoqXZZdQueM\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"ada\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-9yVHft364SlsmdSbm34ihtmI\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"data_prepared.jsonl\",\n \"bytes\": - 18240,\n \"created_at\": 1684164424,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-HkDaoGHV5GLrNOp2eTofvuaT\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 24237,\n \"created_at\": 1684164612,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1684164424,\n \"updated_at\": 1684164612,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"ada:ft-datadog-2023-05-15-15-30-11\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-2WeoESjKDzPQaxueDKYe2vQh\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-gLtI9MieDJLAkvs4JeXE7YjK\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"/Users/anna.pauxberger/Desktop/openai_training_datav0_prepared.jsonl\",\n - \ \"bytes\": 770,\n \"created_at\": 1684167081,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-AP2oVdna7PVuCsQoQjJiOPSJ\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 547,\n \"created_at\": 1684167655,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1684167081,\n \"updated_at\": 1684167656,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"curie:ft-datadog-2023-05-15-16-20-54\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-sEhL5uHgKgjOY4CHhPU730Eq\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"ada\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-DX2IaCNp1lFtolZnnTdwgLk2\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"data_prepared.jsonl\",\n \"bytes\": - 89100,\n \"created_at\": 1684168561,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-R0AobPvwrkHAhG8NEhyGIKB8\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 125751,\n \"created_at\": 1684170700,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1684168562,\n \"updated_at\": 1684170701,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"ada:ft-datadog-2023-05-15-17-11-39\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-Z4CWHaFKHRzRzFOrJ5YxjLKt\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"davinci\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-0cBAMbhVDtxHWZ7alTplIJsS\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"bleh.jsonl\",\n \"bytes\": - 41,\n \"created_at\": 1684178627,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-oVPgfn80efEzWDvHAWEy0dWi\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 271,\n \"created_at\": 1684178911,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1684178627,\n \"updated_at\": 1684178912,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"davinci:ft-datadog-2023-05-15-19-28-30\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-ql3xXD0pgmKVul1cxwsuEleZ\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-GRyGBacpscSHrrU1NK2dTFKx\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"hackadog_2023_05_15/monitor_types/diffs_prepared.jsonl\",\n - \ \"bytes\": 66257,\n \"created_at\": 1684196349,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-9kgi9twwuubMp9mIyeNmhIfB\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 13606,\n \"created_at\": 1684196562,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1684196350,\n \"updated_at\": 1684196563,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"curie:ft-datadog:mobile-monitor-type-2023-05-16-00-22-41\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-4jxdoADiSOBQxnJnzreyL2bO\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1,\n \"classification_positive_class\": \" 1\",\n \"compute_classification_metrics\": - true\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-onrz06LQNU0oonJAH1jAJHdn\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"sqli-sample/sqli_sample_prepared_train.jsonl\",\n - \ \"bytes\": 13635,\n \"created_at\": 1684221692,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"validation_files\": - [\n {\n \"object\": \"file\",\n \"id\": \"file-6s71uPPo6xYtLyxvC2nTdx8Z\",\n - \ \"purpose\": \"fine-tune\",\n \"filename\": \"sqli-sample/sqli_sample_prepared_valid.jsonl\",\n - \ \"bytes\": 3638,\n \"created_at\": 1684221695,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"result_files\": - [\n {\n \"object\": \"file\",\n \"id\": \"file-tXQ9oVloAUxylptLvtoI9TLZ\",\n - \ \"purpose\": \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 34759,\n \"created_at\": 1684223819,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1684221695,\n \"updated_at\": 1684223819,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"curie:ft-datadog-2023-05-16-07-56-58\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-Rqv0cWGcCv55GDd8Qcvbt8ol\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"ada\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-UynjNiMGzVCXU9dWmns0STS3\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"data_prepared.jsonl\",\n \"bytes\": - 19240,\n \"created_at\": 1684224417,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [],\n \"created_at\": 1684224417,\n \"updated_at\": - 1684224595,\n \"status\": \"cancelled\",\n \"fine_tuned_model\": null\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-pZsStzZYmQFRBGsEt0pmCoZw\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"ada\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-l94OdT0jQtCQiMcClKeZUlGS\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"data_prepared.jsonl\",\n \"bytes\": - 19310,\n \"created_at\": 1684224740,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-dt8O7hr5qybjiNoYVgGXz2dq\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 24807,\n \"created_at\": 1684226682,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1684224740,\n \"updated_at\": 1684226682,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"ada:ft-datadog-2023-05-16-08-44-41\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-VkfNRptUY5od8ouyVBFCS5CW\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"ada\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-EDL79B0GN6G6dvuMpxrDiEIS\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"data_prepared_comb.jsonl\",\n \"bytes\": - 38950,\n \"created_at\": 1684225491,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-oH06n9Vcnu3wZJNTeeWa0lYP\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 37488,\n \"created_at\": 1684231309,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1684225491,\n \"updated_at\": 1684231310,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"ada:ft-datadog-2023-05-16-10-01-48\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-NILZKSGmKcvMAiAg1N1AWEnb\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"ada\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-yDL0SqS8LMWVkJ9vOE4tmhda\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"data_prepared_comb.jsonl\",\n \"bytes\": - 156640,\n \"created_at\": 1684226497,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-d9AjUOxfponMoemKpwf2RLZF\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 154934,\n \"created_at\": 1684235653,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1684226497,\n \"updated_at\": 1684235653,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"ada:ft-datadog-2023-05-16-11-14-12\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-slWVn5p50nIbXz9dU3ntc5rw\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1,\n \"classification_positive_class\": \"1\",\n \"compute_classification_metrics\": - true\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-ZA8VAjTn1eJdQd36CZNMDopX\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"sqli-sample/sqli_sample_prepared_train_2.jsonl\",\n - \ \"bytes\": 91449,\n \"created_at\": 1684229197,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"validation_files\": - [\n {\n \"object\": \"file\",\n \"id\": \"file-TojnLRgrLQB1oEd25Rwd5iXk\",\n - \ \"purpose\": \"fine-tune\",\n \"filename\": \"sqli-sample/sqli_sample_prepared_valid_2.jsonl\",\n - \ \"bytes\": 22164,\n \"created_at\": 1684229199,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"result_files\": - [\n {\n \"object\": \"file\",\n \"id\": \"file-eCN5aUvSCi2c84znY4YhOM8y\",\n - \ \"purpose\": \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 197009,\n \"created_at\": 1684236147,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1684229199,\n \"updated_at\": 1684236148,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"curie:ft-datadog-2023-05-16-11-22-26\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-No7BIzIZe43KvmH6Vtl1G5Xs\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"ada\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-erAWhcbGW4T9RUGjrd1yA74s\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"data_prepared.jsonl\",\n \"bytes\": - 265320,\n \"created_at\": 1684232842,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-C7BHnrA5AtAD0fjBmmzcSJ7I\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 181582,\n \"created_at\": 1684239223,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1684232842,\n \"updated_at\": 1684239223,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"ada:ft-datadog-2023-05-16-12-13-42\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-ggYF19fR0PF1OTnUZPXHEb5W\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 2,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"ada\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-cxGN0bdxQgOnWWMcP5XiJGbQ\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"data_prepared.jsonl\",\n \"bytes\": - 513270,\n \"created_at\": 1684240072,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-bzqhZlAd6DfD9dhG3XDJX9bG\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 155083,\n \"created_at\": 1684243394,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1684240073,\n \"updated_at\": 1684243395,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"ada:ft-datadog-2023-05-16-13-23-13\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-ZA0LtY2OfTSlsP5sRLrHgvO2\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 2,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"ada\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-b6NRmgCoBjIRLwfFJXRNjK0E\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"data_prepared.jsonl\",\n \"bytes\": - 735334,\n \"created_at\": 1684240901,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-mTQFJJ4soT3mSasMHfibbtjk\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 190364,\n \"created_at\": 1684247560,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1684240901,\n \"updated_at\": 1684247561,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"ada:ft-datadog-2023-05-16-14-32-39\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-EQ8ytwumIrgT0Us6k8VQvv8e\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1,\n \"classification_positive_class\": \" 1\",\n \"compute_classification_metrics\": - true\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-c6fHTOi6VZT89Egl7NllKBhn\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"sqli-sample/sqli_sample_prepared_train_3.jsonl\",\n - \ \"bytes\": 94953,\n \"created_at\": 1684242636,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"validation_files\": - [\n {\n \"object\": \"file\",\n \"id\": \"file-R8v9JH72cR3QUZz53BXNi7Yh\",\n - \ \"purpose\": \"fine-tune\",\n \"filename\": \"sqli-sample/sqli_sample_prepared_valid_3.jsonl\",\n - \ \"bytes\": 23040,\n \"created_at\": 1684242638,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"result_files\": - [\n {\n \"object\": \"file\",\n \"id\": \"file-yIU3Ospni9s3cF76cUT0Fu6L\",\n - \ \"purpose\": \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 197700,\n \"created_at\": 1684247618,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1684242638,\n \"updated_at\": 1684247618,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"curie:ft-datadog-2023-05-16-14-33-37\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-olWHH230BIs5yFz8RTmu3loi\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1,\n \"classification_positive_class\": \" 0\",\n \"compute_classification_metrics\": - true\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-LeGeDVEgVCwXx0S1W7NaqG2x\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"sqli-sample/sqli_sample_prepared_train_4.jsonl\",\n - \ \"bytes\": 104915,\n \"created_at\": 1684250866,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"validation_files\": - [\n {\n \"object\": \"file\",\n \"id\": \"file-eHo6DYQsoTtuRT1x7m9UIdX6\",\n - \ \"purpose\": \"fine-tune\",\n \"filename\": \"sqli-sample/sqli_sample_prepared_valid_4.jsonl\",\n - \ \"bytes\": 25134,\n \"created_at\": 1684250869,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"result_files\": - [\n {\n \"object\": \"file\",\n \"id\": \"file-ADhhXk4VxbM8DQ5Z2VhGM5eI\",\n - \ \"purpose\": \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 215866,\n \"created_at\": 1684254357,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1684250869,\n \"updated_at\": 1684254358,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"curie:ft-datadog-2023-05-16-16-25-56\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-6ZIfqHLOkv4evlFYTpc1ISry\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-cBvVu5bekOrx0NIrNBmW7gIc\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"file\",\n \"bytes\": 602,\n - \ \"created_at\": 1684251156,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-vCHipSgdL3IIdH5Y0mk4Iwso\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 753,\n \"created_at\": 1684258665,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1684251156,\n \"updated_at\": 1684258666,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"curie:ft-datadog-2023-05-16-17-37-44\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-eXaHaNRl4E4EmXgS4GCIan1f\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-mKA7CfQJTkfWQLQ022mxQQmh\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"file\",\n \"bytes\": 602,\n - \ \"created_at\": 1684252252,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-4p4ADCbFs5u3Ab0jakZFtXp5\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 758,\n \"created_at\": 1684260495,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1684252252,\n \"updated_at\": 1684260495,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"curie:ft-datadog-2023-05-16-18-08-14\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-Gsl0Kdpx5PrkdtUTb9qJmb4A\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 2,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.2\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-1ybAxD6DjBTRg1ljpFk1glus\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"/Users/mac.mccarthy/Desktop/prompts_jsonl_for_documentation_tuning_prepared.jsonl\",\n - \ \"bytes\": 10204795,\n \"created_at\": 1684256310,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-jJzVhzb0omnycOMayDzboMlL\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 131719,\n \"created_at\": 1684262191,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1684256310,\n \"updated_at\": 1684262192,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"curie:ft-datadog-2023-05-16-18-36-30\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-Zjix9fd4XfhFNx5FhH72D89e\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-zacazVNNLWU7dRBeC1dymHWR\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"da_prepared (1).jsonl\",\n \"bytes\": - 4770,\n \"created_at\": 1684264681,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-RnPEQRhqru539eMxbkifDeIP\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 2634,\n \"created_at\": 1684266455,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1684264681,\n \"updated_at\": 1684266456,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"curie:ft-datadog-2023-05-16-19-47-34\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-KEq2ECgvFvjKxkVYehWet1mR\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"ada\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-fIkEUgQPWnVXNKPJsr4pEWiz\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"training_data_prepared_train.jsonl\",\n - \ \"bytes\": 197504,\n \"created_at\": 1684266989,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [],\n \"created_at\": 1684266989,\n \"updated_at\": - 1684267793,\n \"status\": \"cancelled\",\n \"fine_tuned_model\": null\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-ox7eftHC7yFccgfWlsm7EQd1\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-xC22NUuYBkXvzRt2fLREcGde\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"file\",\n \"bytes\": 147697,\n - \ \"created_at\": 1684277658,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-kXHUNqbfOZJhdgz7vFF0xoHc\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 169003,\n \"created_at\": 1684280547,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1684277751,\n \"updated_at\": 1684280548,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"curie:ft-datadog-2023-05-16-23-42-26\"\n },\n - \ {\n \"object\": \"fine-tune\",\n \"id\": \"ft-mmj9smspyQETzn6Fwij6Ye0K\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - null,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - null\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-xC22NUuYBkXvzRt2fLREcGde\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"file\",\n \"bytes\": 147697,\n - \ \"created_at\": 1684277658,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [],\n \"created_at\": 1684277798,\n \"updated_at\": - 1684277868,\n \"status\": \"cancelled\",\n \"fine_tuned_model\": null\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-6MJYZPQ1P2VoX6Irqwjf7MVz\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - null,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - null\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-xC22NUuYBkXvzRt2fLREcGde\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"file\",\n \"bytes\": 147697,\n - \ \"created_at\": 1684277658,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [],\n \"created_at\": 1684278059,\n \"updated_at\": - 1684278096,\n \"status\": \"cancelled\",\n \"fine_tuned_model\": null\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-doj9zLo3hxxviakrrdx9BO42\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - null,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - null\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-xC22NUuYBkXvzRt2fLREcGde\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"file\",\n \"bytes\": 147697,\n - \ \"created_at\": 1684277658,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [],\n \"created_at\": 1684440495,\n \"updated_at\": - 1684440598,\n \"status\": \"cancelled\",\n \"fine_tuned_model\": null\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-10RCfqSvgyEcauomw7VpiYco\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 3,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-t3k1gVSQDHrfZnPckzftlZ4A\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"dave-hal.jsonl\",\n \"bytes\": - 356,\n \"created_at\": 1684365950,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-bJyf8TM0jeSZueBo4jpodZVQ\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 410,\n \"created_at\": 1684442697,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1684442489,\n \"updated_at\": 1684442697,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"curie:ft-datadog:deleteme-2023-05-18-20-44-56\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-2jU5ALzNgX2ELktqfARJubYy\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 3,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-t3k1gVSQDHrfZnPckzftlZ4A\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"dave-hal.jsonl\",\n \"bytes\": - 356,\n \"created_at\": 1684365950,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-81juw46uj7dR03Na4UwmpMg8\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 393,\n \"created_at\": 1684452342,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1684452081,\n \"updated_at\": 1684452342,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"curie:ft-datadog:deleteme-2023-05-18-23-25-41\"\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-TVpNqwlvermMegfRVqSOyPyS\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 3,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-t3k1gVSQDHrfZnPckzftlZ4A\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"dave-hal.jsonl\",\n \"bytes\": - 356,\n \"created_at\": 1684365950,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [],\n \"created_at\": 1684452102,\n \"updated_at\": - 1684452103,\n \"status\": \"cancelled\",\n \"fine_tuned_model\": null\n - \ },\n {\n \"object\": \"fine-tune\",\n \"id\": \"ft-n8jruCQHgfff5dnhDC1kmEAw\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": - 1,\n \"prompt_loss_weight\": 0.01,\n \"learning_rate_multiplier\": - 0.1\n },\n \"organization_id\": \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n - \ \"model\": \"curie\",\n \"training_files\": [\n {\n \"object\": - \"file\",\n \"id\": \"file-llDq0Q9la7EBTScAowIotxxc\",\n \"purpose\": - \"fine-tune\",\n \"filename\": \"yun_file.jsonl\",\n \"bytes\": - 147697,\n \"created_at\": 1684447571,\n \"status\": \"processed\",\n - \ \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [\n {\n \"object\": \"file\",\n - \ \"id\": \"file-6QnU9h34M6aeQGxeeLwT4RGg\",\n \"purpose\": - \"fine-tune-results\",\n \"filename\": \"compiled_results.csv\",\n - \ \"bytes\": 168937,\n \"created_at\": 1684508093,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"created_at\": - 1684507190,\n \"updated_at\": 1684508093,\n \"status\": \"succeeded\",\n - \ \"fine_tuned_model\": \"curie:ft-datadog-2023-05-19-14-54-52\"\n }\n - \ ],\n \"next_starting_after\": null\n}\n" - headers: - CF-Cache-Status: - - DYNAMIC - CF-RAY: - - 81bd2d732b6c1996-EWR - Connection: - - keep-alive - Content-Encoding: - - gzip - Content-Type: - - application/json - Date: - - Wed, 25 Oct 2023 20:23:29 GMT - Server: - - cloudflare - Transfer-Encoding: - - chunked - access-control-allow-origin: - - '*' - alt-svc: - - h3=":443"; ma=86400 - openai-processing-ms: - - '709' - openai-version: - - '2020-10-01' - strict-transport-security: - - max-age=15724800; includeSubDomains - x-request-id: - - d05cab5e321bf2ee34600cdbbfa900d4 - http_version: HTTP/1.1 - status_code: 200 -version: 1 diff --git a/tests/contrib/openai/cassettes/v1/fine_tune_list_events.yaml b/tests/contrib/openai/cassettes/v1/fine_tune_list_events.yaml deleted file mode 100644 index 6338621d00f..00000000000 --- a/tests/contrib/openai/cassettes/v1/fine_tune_list_events.yaml +++ /dev/null @@ -1,69 +0,0 @@ -interactions: -- request: - body: '' - headers: - accept: - - application/json - accept-encoding: - - gzip, deflate - connection: - - keep-alive - content-type: - - application/json - host: - - api.openai.com - user-agent: - - OpenAI/Python 1.0.0-beta.3 - x-stainless-arch: - - arm64 - x-stainless-lang: - - python - x-stainless-os: - - MacOS - x-stainless-package-version: - - 1.0.0-beta.3 - x-stainless-runtime: - - CPython - x-stainless-runtime-version: - - 3.10.5 - method: GET - uri: https://api.openai.com/v1/fine-tunes/ft-N6ggcFNqJNuREixR9ShDWzST/events?stream=false - response: - content: "{\n \"object\": \"list\",\n \"data\": [\n {\n \"object\": - \"fine-tune-event\",\n \"level\": \"info\",\n \"message\": \"Created - fine-tune: ft-N6ggcFNqJNuREixR9ShDWzST\",\n \"created_at\": 1685663704\n - \ },\n {\n \"object\": \"fine-tune-event\",\n \"level\": \"info\",\n - \ \"message\": \"Fine-tune cancelled\",\n \"created_at\": 1685663725\n - \ }\n ]\n}\n" - headers: - CF-Cache-Status: - - DYNAMIC - CF-RAY: - - 81c661e9cb420f7f-EWR - Connection: - - keep-alive - Content-Encoding: - - gzip - Content-Type: - - application/json - Date: - - Thu, 26 Oct 2023 23:12:09 GMT - Server: - - cloudflare - Transfer-Encoding: - - chunked - access-control-allow-origin: - - '*' - alt-svc: - - h3=":443"; ma=86400 - openai-processing-ms: - - '57' - openai-version: - - '2020-10-01' - strict-transport-security: - - max-age=15724800; includeSubDomains - x-request-id: - - a449df26e28097f002450bc9cb122b5b - http_version: HTTP/1.1 - status_code: 200 -version: 1 diff --git a/tests/contrib/openai/cassettes/v1/fine_tune_retrieve.yaml b/tests/contrib/openai/cassettes/v1/fine_tune_retrieve.yaml deleted file mode 100644 index ee55495e909..00000000000 --- a/tests/contrib/openai/cassettes/v1/fine_tune_retrieve.yaml +++ /dev/null @@ -1,78 +0,0 @@ -interactions: -- request: - body: '' - headers: - accept: - - application/json - accept-encoding: - - gzip, deflate - connection: - - keep-alive - content-type: - - application/json - host: - - api.openai.com - user-agent: - - OpenAI/Python 1.0.0-beta.3 - x-stainless-arch: - - arm64 - x-stainless-lang: - - python - x-stainless-os: - - MacOS - x-stainless-package-version: - - 1.0.0-beta.3 - x-stainless-runtime: - - CPython - x-stainless-runtime-version: - - 3.10.5 - method: GET - uri: https://api.openai.com/v1/fine-tunes/ft-sADEaavxRFrjOQ65XkQKm0zM - response: - content: "{\n \"object\": \"fine-tune\",\n \"id\": \"ft-sADEaavxRFrjOQ65XkQKm0zM\",\n - \ \"hyperparams\": {\n \"n_epochs\": 4,\n \"batch_size\": 5,\n \"prompt_loss_weight\": - 0.01,\n \"learning_rate_multiplier\": 0.05\n },\n \"organization_id\": - \"org-OS4zcsDN8sF8E8CdhxgMkBV4\",\n \"model\": \"babbage\",\n \"training_files\": - [\n {\n \"object\": \"file\",\n \"id\": \"file-llDq0Q9la7EBTScAowIotxxc\",\n - \ \"purpose\": \"fine-tune\",\n \"filename\": \"yun_file.jsonl\",\n - \ \"bytes\": 147697,\n \"created_at\": 1685662960,\n \"status\": - \"processed\",\n \"status_details\": null\n }\n ],\n \"validation_files\": - [],\n \"result_files\": [],\n \"created_at\": 1685662960,\n \"updated_at\": - 1685663003,\n \"status\": \"cancelled\",\n \"fine_tuned_model\": null,\n \"events\": - [\n {\n \"object\": \"fine-tune-event\",\n \"level\": \"info\",\n - \ \"message\": \"Created fine-tune: ft-sADEaavxRFrjOQ65XkQKm0zM\",\n \"created_at\": - 1685662960\n },\n {\n \"object\": \"fine-tune-event\",\n \"level\": - \"info\",\n \"message\": \"Fine-tune cancelled\",\n \"created_at\": - 1685662960\n }\n ]\n}\n" - headers: - CF-Cache-Status: - - DYNAMIC - CF-RAY: - - 81c6308edfd94270-EWR - Connection: - - keep-alive - Content-Encoding: - - gzip - Content-Type: - - application/json - Date: - - Thu, 26 Oct 2023 22:38:27 GMT - Server: - - cloudflare - Transfer-Encoding: - - chunked - access-control-allow-origin: - - '*' - alt-svc: - - h3=":443"; ma=86400 - openai-processing-ms: - - '41' - openai-version: - - '2020-10-01' - strict-transport-security: - - max-age=15724800; includeSubDomains - x-request-id: - - 341b34c8d07352eb98682d0cb724d2f3 - http_version: HTTP/1.1 - status_code: 200 -version: 1 diff --git a/tests/contrib/openai/test_openai_v0.py b/tests/contrib/openai/test_openai_v0.py index 64f79f755c4..0175c8cad0d 100644 --- a/tests/contrib/openai/test_openai_v0.py +++ b/tests/contrib/openai/test_openai_v0.py @@ -494,89 +494,6 @@ async def test_achat_completion(api_key_in_env, request_api_key, openai, openai_ ) -@pytest.mark.parametrize("api_key_in_env", [True, False]) -def test_edit(api_key_in_env, request_api_key, openai, openai_vcr, snapshot_tracer): - if not hasattr(openai, "Edit"): - pytest.skip("edit not supported for this version of openai") - with snapshot_context( - token="tests.contrib.openai.test_openai.test_edit", ignores=["meta.http.useragent", "meta.openai.base_url"] - ): - with openai_vcr.use_cassette("edit.yaml"): - openai.Edit.create( - api_key=request_api_key, - model="text-davinci-edit-001", - input="thsi si a spelilgn imstkae.", - instruction="fix spelling mistakes", - n=3, - temperature=0.2, - user="ddtrace-test", - ) - - -@pytest.mark.asyncio -@pytest.mark.parametrize("api_key_in_env", [True, False]) -async def test_aedit(api_key_in_env, request_api_key, openai, openai_vcr, snapshot_tracer): - if not hasattr(openai, "Edit"): - pytest.skip("edit not supported for this version of openai") - with snapshot_context( - token="tests.contrib.openai.test_openai.test_edit", ignores=["meta.http.useragent", "meta.openai.base_url"] - ): - with openai_vcr.use_cassette("edit_async.yaml"): - await openai.Edit.acreate( - api_key=request_api_key, - model="text-davinci-edit-001", - input="thsi si a spelilgn imstkae.", - instruction="fix spelling mistakes", - n=3, - temperature=0.2, - user="ddtrace-test", - ) - - -@pytest.mark.parametrize("ddtrace_config_openai", [dict(logs_enabled=True, log_prompt_completion_sample_rate=1.0)]) -def test_logs_edit(openai_vcr, openai, ddtrace_config_openai, mock_logs, mock_tracer): - """Ensure logs are emitted for edit endpoint when configured. - - Also ensure the logs have the correct tagging including the trace-logs correlation tagging. - """ - if not hasattr(openai, "Edit"): - pytest.skip("edit not supported for this version of openai") - with openai_vcr.use_cassette("edit.yaml"): - openai.Edit.create( - model="text-davinci-edit-001", - input="thsi si a spelilgn imstkae.", - instruction="fix spelling mistakes", - n=3, - temperature=0.2, - user="ddtrace-test", - ) - span = mock_tracer.pop_traces()[0][0] - trace_id, span_id = span.trace_id, span.span_id - - assert mock_logs.enqueue.call_count == 1 - mock_logs.assert_has_calls( - [ - mock.call.start(), - mock.call.enqueue( - { - "timestamp": mock.ANY, - "message": mock.ANY, - "hostname": mock.ANY, - "ddsource": "openai", - "service": "", - "status": "info", - "ddtags": "env:,version:,openai.request.endpoint:/v1/edits,openai.request.method:POST,openai.request.model:text-davinci-edit-001,openai.organization.name:datadog-4,openai.user.api_key:sk-...key>", # noqa: E501 - "dd.trace_id": "{:x}".format(trace_id), - "dd.span_id": str(span_id), - "instruction": "fix spelling mistakes", - "input": "thsi si a spelilgn imstkae.", - "choices": mock.ANY, - } - ), - ] - ) - - @pytest.mark.parametrize("api_key_in_env", [True, False]) def test_image_create(api_key_in_env, request_api_key, openai, openai_vcr, snapshot_tracer): if not hasattr(openai, "Image"): @@ -1277,162 +1194,6 @@ async def test_file_adownload(api_key_in_env, request_api_key, openai, openai_vc ) -@pytest.mark.parametrize("api_key_in_env", [True, False]) -def test_fine_tune_list(api_key_in_env, request_api_key, openai, openai_vcr, snapshot_tracer): - if not hasattr(openai, "FineTune"): - pytest.skip("fine tunes not supported for this version of openai") - with snapshot_context( - token="tests.contrib.openai.test_openai.test_fine_tune_list", - ignores=["meta.http.useragent", "meta.openai.base_url"], - ): - with openai_vcr.use_cassette("fine_tune_list.yaml"): - openai.FineTune.list( - api_key=request_api_key, - user="ddtrace-test", - ) - - -@pytest.mark.asyncio -@pytest.mark.parametrize("api_key_in_env", [True, False]) -async def test_fine_tune_alist(api_key_in_env, request_api_key, openai, openai_vcr, snapshot_tracer): - if not hasattr(openai, "FineTune"): - pytest.skip("fine tunes not supported for this version of openai") - with snapshot_context( - token="tests.contrib.openai.test_openai.test_fine_tune_list", - ignores=["meta.http.useragent", "meta.openai.base_url"], - ): - with openai_vcr.use_cassette("fine_tune_list_async.yaml"): - await openai.FineTune.alist( - api_key=request_api_key, - user="ddtrace-test", - ) - - -@pytest.mark.parametrize("api_key_in_env", [True, False]) -def test_fine_tune_create(api_key_in_env, request_api_key, openai, openai_vcr, snapshot_tracer): - if not hasattr(openai, "FineTune"): - pytest.skip("fine tunes not supported for this version of openai") - with snapshot_context( - token="tests.contrib.openai.test_openai.test_fine_tune_create", - ignores=[ - "meta.http.useragent", - "meta.openai.base_url", - "meta.openai.request.classification_n_classes", - "meta.openai.request.classification_positive_class", - "meta.openai.request.validation_file", - "metrics.openai.request.classification_betas_count", - ], - ): - with openai_vcr.use_cassette("fine_tune_create.yaml"): - openai.FineTune.create( - training_file="file-llDq0Q9la7EBTScAowIotxxc", - n_epochs=4, - prompt_loss_weight=0.01, - model="babbage", - suffix="dummy-fine-tune-model", - batch_size=5, - learning_rate_multiplier=0.05, - compute_classification_metrics=False, - api_key=request_api_key, - ) - - -@pytest.mark.asyncio -@pytest.mark.parametrize("api_key_in_env", [True, False]) -async def test_fine_tune_acreate(api_key_in_env, request_api_key, openai, openai_vcr, snapshot_tracer): - if not hasattr(openai, "FineTune"): - pytest.skip("fine tunes not supported for this version of openai") - with snapshot_context( - token="tests.contrib.openai.test_openai.test_fine_tune_create", - ignores=[ - "meta.http.useragent", - "meta.openai.base_url", - "meta.openai.request.classification_n_classes", - "meta.openai.request.classification_positive_class", - "meta.openai.request.validation_file", - "metrics.openai.request.classification_betas_count", - ], - ): - with openai_vcr.use_cassette("fine_tune_create_async.yaml"): - await openai.FineTune.acreate( - training_file="file-llDq0Q9la7EBTScAowIotxxc", - n_epochs=4, - prompt_loss_weight=0.01, - model="babbage", - suffix="dummy-fine-tune-model", - batch_size=5, - learning_rate_multiplier=0.05, - compute_classification_metrics=False, - api_key=request_api_key, - ) - - -@pytest.mark.parametrize("api_key_in_env", [True, False]) -def test_fine_tune_retrieve(api_key_in_env, request_api_key, openai, openai_vcr, snapshot_tracer): - if not hasattr(openai, "FineTune"): - pytest.skip("fine tunes not supported for this version of openai") - with snapshot_context( - token="tests.contrib.openai.test_openai.test_fine_tune_retrieve", - ignores=["meta.http.useragent", "meta.openai.base_url"], - ): - with openai_vcr.use_cassette("fine_tune_retrieve.yaml"): - openai.FineTune.retrieve( - id="ft-sADEaavxRFrjOQ65XkQKm0zM", - api_key=request_api_key, - user="ddtrace-test", - ) - - -@pytest.mark.asyncio -@pytest.mark.parametrize("api_key_in_env", [True, False]) -async def test_fine_tune_aretrieve(api_key_in_env, request_api_key, openai, openai_vcr, snapshot_tracer): - if not hasattr(openai, "FineTune"): - pytest.skip("fine tunes not supported for this version of openai") - with snapshot_context( - token="tests.contrib.openai.test_openai.test_fine_tune_retrieve", - ignores=["meta.http.useragent", "meta.openai.base_url"], - ): - with openai_vcr.use_cassette("fine_tune_retrieve_async.yaml"): - await openai.FineTune.aretrieve( - id="ft-sADEaavxRFrjOQ65XkQKm0zM", - api_key=request_api_key, - user="ddtrace-test", - ) - - -@pytest.mark.parametrize("api_key_in_env", [True, False]) -def test_fine_tune_cancel(api_key_in_env, request_api_key, openai, openai_vcr, snapshot_tracer): - if not hasattr(openai, "FineTune"): - pytest.skip("fine tunes not supported for this version of openai") - with snapshot_context( - token="tests.contrib.openai.test_openai.test_fine_tune_cancel", - ignores=["meta.http.useragent", "meta.openai.base_url"], - ): - with openai_vcr.use_cassette("fine_tune_cancel.yaml"): - openai.FineTune.cancel( - id="ft-N6ggcFNqJNuREixR9ShDWzST", - api_key=request_api_key, - user="ddtrace-test", - ) - - -@pytest.mark.asyncio -@pytest.mark.parametrize("api_key_in_env", [True, False]) -async def test_fine_tune_acancel(api_key_in_env, request_api_key, openai, openai_vcr, snapshot_tracer): - if not hasattr(openai, "FineTune"): - pytest.skip("fine tunes not supported for this version of openai") - with snapshot_context( - token="tests.contrib.openai.test_openai.test_fine_tune_cancel", - ignores=["meta.http.useragent", "meta.openai.base_url"], - ): - with openai_vcr.use_cassette("fine_tune_cancel_async.yaml"): - await openai.FineTune.acancel( - id="ft-N6ggcFNqJNuREixR9ShDWzST", - api_key=request_api_key, - user="ddtrace-test", - ) - - @pytest.mark.parametrize("api_key_in_env", [True, False]) def test_model_delete(api_key_in_env, request_api_key, openai, openai_vcr, snapshot_tracer): if not hasattr(openai, "Model"): @@ -1466,20 +1227,6 @@ async def test_model_adelete(api_key_in_env, request_api_key, openai, openai_vcr ) -@pytest.mark.parametrize("api_key_in_env", [True, False]) -def test_fine_tune_list_events(api_key_in_env, request_api_key, openai, openai_vcr, snapshot_tracer): - if not hasattr(openai, "FineTune"): - pytest.skip("fine tunes not supported for this version of openai") - with snapshot_context( - token="tests.contrib.openai.test_openai.test_fine_tune_list_events", - ignores=["meta.http.useragent", "meta.openai.base_url"], - ): - with openai_vcr.use_cassette("fine_tune_list_events.yaml"): - openai.FineTune.list_events( - id="ft-N6ggcFNqJNuREixR9ShDWzST", api_key=request_api_key, stream=False, user="ddtrace-test" - ) - - @pytest.mark.parametrize("api_key_in_env", [True, False]) def test_create_moderation(api_key_in_env, request_api_key, openai, openai_vcr, snapshot_tracer): if not hasattr(openai, "Moderation"): diff --git a/tests/contrib/openai/test_openai_v1.py b/tests/contrib/openai/test_openai_v1.py index e8928844d17..b5383222efa 100644 --- a/tests/contrib/openai/test_openai_v1.py +++ b/tests/contrib/openai/test_openai_v1.py @@ -77,8 +77,6 @@ def test_patching(openai): (openai.resources.models.Models, "retrieve"), (openai.resources.models.AsyncModels, "list"), (openai.resources.models.AsyncModels, "retrieve"), - (openai.resources.edits.Edits, "create"), - (openai.resources.edits.AsyncEdits, "create"), (openai.resources.images.Images, "generate"), (openai.resources.images.Images, "edit"), (openai.resources.images.Images, "create_variation"), @@ -101,16 +99,6 @@ def test_patching(openai): (openai.resources.files.AsyncFiles, "list"), (openai.resources.files.AsyncFiles, "delete"), (openai.resources.files.AsyncFiles, "retrieve_content"), - (openai.resources.fine_tunes.FineTunes, "create"), - (openai.resources.fine_tunes.FineTunes, "retrieve"), - (openai.resources.fine_tunes.FineTunes, "list"), - (openai.resources.fine_tunes.FineTunes, "cancel"), - (openai.resources.fine_tunes.FineTunes, "list_events"), - (openai.resources.fine_tunes.AsyncFineTunes, "create"), - (openai.resources.fine_tunes.AsyncFineTunes, "retrieve"), - (openai.resources.fine_tunes.AsyncFineTunes, "list"), - (openai.resources.fine_tunes.AsyncFineTunes, "cancel"), - (openai.resources.fine_tunes.AsyncFineTunes, "list_events"), ] for m in methods: @@ -565,83 +553,6 @@ async def test_achat_completion(api_key_in_env, request_api_key, openai, openai_ ) -@pytest.mark.parametrize("api_key_in_env", [True, False]) -def test_edit(api_key_in_env, request_api_key, openai, openai_vcr, snapshot_tracer): - with snapshot_context( - token="tests.contrib.openai.test_openai.test_edit", - ignores=["meta.http.useragent", "meta.openai.api_type", "meta.openai.api_base", "meta.openai.request.user"], - ): - with openai_vcr.use_cassette("edit.yaml"): - client = openai.OpenAI(api_key=request_api_key) - client.edits.create( - model="text-davinci-edit-001", - input="thsi si a spelilgn imstkae.", - instruction="fix spelling mistakes", - n=3, - temperature=0.2, - ) - - -@pytest.mark.asyncio -@pytest.mark.parametrize("api_key_in_env", [True, False]) -async def test_aedit(api_key_in_env, request_api_key, openai, openai_vcr, snapshot_tracer): - with snapshot_context( - token="tests.contrib.openai.test_openai.test_edit", - ignores=["meta.http.useragent", "meta.openai.api_type", "meta.openai.api_base", "meta.openai.request.user"], - ): - with openai_vcr.use_cassette("edit_async.yaml"): - client = openai.AsyncOpenAI(api_key=request_api_key) - await client.edits.create( - model="text-davinci-edit-001", - input="thsi si a spelilgn imstkae.", - instruction="fix spelling mistakes", - n=3, - temperature=0.2, - ) - - -@pytest.mark.parametrize("ddtrace_config_openai", [dict(logs_enabled=True, log_prompt_completion_sample_rate=1.0)]) -def test_logs_edit(openai_vcr, openai, ddtrace_config_openai, mock_logs, mock_tracer): - """Ensure logs are emitted for edit endpoint when configured. - - Also ensure the logs have the correct tagging including the trace-logs correlation tagging. - """ - with openai_vcr.use_cassette("edit.yaml"): - client = openai.OpenAI() - client.edits.create( - model="text-davinci-edit-001", - input="thsi si a spelilgn imstkae.", - instruction="fix spelling mistakes", - n=3, - temperature=0.2, - ) - span = mock_tracer.pop_traces()[0][0] - trace_id, span_id = span.trace_id, span.span_id - - assert mock_logs.enqueue.call_count == 1 - mock_logs.assert_has_calls( - [ - mock.call.start(), - mock.call.enqueue( - { - "timestamp": mock.ANY, - "message": mock.ANY, - "hostname": mock.ANY, - "ddsource": "openai", - "service": "", - "status": "info", - "ddtags": "env:,version:,openai.request.endpoint:/v1/edits,openai.request.method:POST,openai.request.model:text-davinci-edit-001,openai.organization.name:datadog-4,openai.user.api_key:sk-...key>", # noqa: E501 - "dd.trace_id": "{:x}".format(trace_id), - "dd.span_id": str(span_id), - "instruction": "fix spelling mistakes", - "input": "thsi si a spelilgn imstkae.", - "choices": mock.ANY, - } - ), - ] - ) - - @pytest.mark.parametrize("api_key_in_env", [True, False]) def test_image_create(api_key_in_env, request_api_key, openai, openai_vcr, snapshot_tracer): with snapshot_context( @@ -946,124 +857,6 @@ async def test_file_adownload(api_key_in_env, request_api_key, openai, openai_vc ) -@pytest.mark.parametrize("api_key_in_env", [True, False]) -def test_fine_tune_list(api_key_in_env, request_api_key, openai, openai_vcr, snapshot_tracer): - with snapshot_context( - token="tests.contrib.openai.test_openai.test_fine_tune_list", - ignores=["meta.http.useragent", "meta.openai.api_type", "meta.openai.api_base", "meta.openai.request.user"], - ): - with openai_vcr.use_cassette("fine_tune_list.yaml"): - client = openai.OpenAI(api_key=request_api_key) - client.fine_tunes.list() - - -@pytest.mark.asyncio -@pytest.mark.parametrize("api_key_in_env", [True, False]) -async def test_fine_tune_alist(api_key_in_env, request_api_key, openai, openai_vcr, snapshot_tracer): - with snapshot_context( - token="tests.contrib.openai.test_openai.test_fine_tune_list", - ignores=["meta.http.useragent", "meta.openai.api_type", "meta.openai.api_base", "meta.openai.request.user"], - ): - with openai_vcr.use_cassette("fine_tune_list.yaml"): - client = openai.AsyncOpenAI(api_key=request_api_key) - await client.fine_tunes.list() - - -@pytest.mark.parametrize("api_key_in_env", [True, False]) -def test_fine_tune_create(api_key_in_env, request_api_key, openai, openai_vcr, snapshot_tracer): - with snapshot_context( - token="tests.contrib.openai.test_openai.test_fine_tune_create", - ignores=["meta.http.useragent", "meta.openai.api_type", "meta.openai.api_base", "meta.openai.request.n_epochs"], - ): - with openai_vcr.use_cassette("fine_tune_create.yaml"): - client = openai.OpenAI(api_key=request_api_key) - client.fine_tunes.create( - training_file="file-llDq0Q9la7EBTScAowIotxxc", - prompt_loss_weight=0.01, - model="babbage", - suffix="dummy-fine-tune-model", - batch_size=5, - learning_rate_multiplier=0.05, - compute_classification_metrics=False, - ) - - -@pytest.mark.asyncio -@pytest.mark.parametrize("api_key_in_env", [True, False]) -async def test_fine_tune_acreate(api_key_in_env, request_api_key, openai, openai_vcr, snapshot_tracer): - with snapshot_context( - token="tests.contrib.openai.test_openai.test_fine_tune_create", - ignores=["meta.http.useragent", "meta.openai.api_type", "meta.openai.api_base", "meta.openai.request.n_epochs"], - ): - with openai_vcr.use_cassette("fine_tune_create.yaml"): - client = openai.AsyncOpenAI(api_key=request_api_key) - await client.fine_tunes.create( - training_file="file-llDq0Q9la7EBTScAowIotxxc", - prompt_loss_weight=0.01, - model="babbage", - suffix="dummy-fine-tune-model", - batch_size=5, - learning_rate_multiplier=0.05, - compute_classification_metrics=False, - ) - - -@pytest.mark.parametrize("api_key_in_env", [True, False]) -def test_fine_tune_retrieve(api_key_in_env, request_api_key, openai, openai_vcr, snapshot_tracer): - with snapshot_context( - token="tests.contrib.openai.test_openai.test_fine_tune_retrieve", - ignores=["meta.http.useragent", "meta.openai.api_type", "meta.openai.api_base", "meta.openai.request.user"], - ): - with openai_vcr.use_cassette("fine_tune_retrieve.yaml"): - client = openai.OpenAI(api_key=request_api_key) - client.fine_tunes.retrieve( - fine_tune_id="ft-sADEaavxRFrjOQ65XkQKm0zM", - ) - - -@pytest.mark.parametrize("api_key_in_env", [True, False]) -def test_fine_tune_cancel(api_key_in_env, request_api_key, openai, openai_vcr, snapshot_tracer): - with snapshot_context( - token="tests.contrib.openai.test_openai.test_fine_tune_cancel", - ignores=[ - "meta.http.useragent", - "meta.openai.api_type", - "meta.openai.api_base", - "meta.openai.request.user", - "meta.openai.response.hyperparams.batch_size", - "meta.openai.response.hyperparams.learning_rate_multiplier", - "meta.openai.response.hyperparams.batch_size", - ], - ): - with openai_vcr.use_cassette("fine_tune_cancel.yaml"): - client = openai.OpenAI(api_key=request_api_key) - client.fine_tunes.cancel( - fine_tune_id="ft-N6ggcFNqJNuREixR9ShDWzST", - ) - - -@pytest.mark.asyncio -@pytest.mark.parametrize("api_key_in_env", [True, False]) -async def test_fine_tune_acancel(api_key_in_env, request_api_key, openai, openai_vcr, snapshot_tracer): - with snapshot_context( - token="tests.contrib.openai.test_openai.test_fine_tune_cancel", - ignores=[ - "meta.http.useragent", - "meta.openai.api_type", - "meta.openai.api_base", - "meta.openai.request.user", - "meta.openai.response.hyperparams.batch_size", - "meta.openai.response.hyperparams.learning_rate_multiplier", - "meta.openai.response.hyperparams.batch_size", - ], - ): - with openai_vcr.use_cassette("fine_tune_cancel.yaml"): - client = openai.AsyncOpenAI(api_key=request_api_key) - await client.fine_tunes.cancel( - fine_tune_id="ft-N6ggcFNqJNuREixR9ShDWzST", - ) - - @pytest.mark.parametrize("api_key_in_env", [True, False]) def test_model_delete(api_key_in_env, request_api_key, openai, openai_vcr, snapshot_tracer): with snapshot_context( @@ -1091,29 +884,6 @@ async def test_model_adelete(api_key_in_env, request_api_key, openai, openai_vcr ) -@pytest.mark.parametrize("api_key_in_env", [True, False]) -def test_fine_tune_list_events(api_key_in_env, request_api_key, openai, openai_vcr, snapshot_tracer): - with snapshot_context( - token="tests.contrib.openai.test_openai.test_fine_tune_list_events", - ignores=["meta.http.useragent", "meta.openai.api_type", "meta.openai.api_base", "meta.openai.request.user"], - ): - with openai_vcr.use_cassette("fine_tune_list_events.yaml"): - client = openai.OpenAI(api_key=request_api_key) - client.fine_tunes.list_events(fine_tune_id="ft-N6ggcFNqJNuREixR9ShDWzST", stream=False) - - -@pytest.mark.asyncio -@pytest.mark.parametrize("api_key_in_env", [True, False]) -async def test_fine_tune_alist_events(api_key_in_env, request_api_key, openai, openai_vcr, snapshot_tracer): - with snapshot_context( - token="tests.contrib.openai.test_openai.test_fine_tune_list_events", - ignores=["meta.http.useragent", "meta.openai.api_type", "meta.openai.api_base", "meta.openai.request.user"], - ): - with openai_vcr.use_cassette("fine_tune_list_events.yaml"): - client = openai.AsyncOpenAI(api_key=request_api_key) - await client.fine_tunes.list_events(fine_tune_id="ft-N6ggcFNqJNuREixR9ShDWzST", stream=False) - - @pytest.mark.parametrize("api_key_in_env", [True, False]) def test_create_moderation(api_key_in_env, request_api_key, openai, openai_vcr, snapshot_tracer): with snapshot_context( diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_edit.json b/tests/snapshots/tests.contrib.openai.test_openai.test_edit.json deleted file mode 100644 index 27a5c0b477f..00000000000 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_edit.json +++ /dev/null @@ -1,49 +0,0 @@ -[[ - { - "name": "openai.request", - "service": "", - "resource": "createEdit", - "trace_id": 0, - "span_id": 1, - "parent_id": 0, - "type": "", - "error": 0, - "meta": { - "_dd.p.dm": "-0", - "_dd.p.tid": "654a694400000000", - "component": "openai", - "language": "python", - "openai.api_base": "https://api.openai.com/v1", - "openai.api_type": "open_ai", - "openai.base_url": "https://api.openai.com/v1/", - "openai.organization.name": "datadog-4", - "openai.request.endpoint": "/v1/edits", - "openai.request.input": "thsi si a spelilgn imstkae.", - "openai.request.instruction": "fix spelling mistakes", - "openai.request.method": "POST", - "openai.request.model": "text-davinci-edit-001", - "openai.request.n": "3", - "openai.request.temperature": "0.2", - "openai.request.user": "ddtrace-test", - "openai.response.choices.0.text": "this is a spelling mistake.\\n", - "openai.response.choices.1.text": "this is a spelling mistake.\\n", - "openai.response.choices.2.text": "this is a spelling mistake.\\n", - "openai.response.created": "1684789878", - "openai.user.api_key": "sk-...key>", - "runtime-id": "e3412e4f1afe4767a615b30fc1c0f91d" - }, - "metrics": { - "_dd.measured": 1, - "_dd.top_level": 1, - "_dd.tracer_kr": 1.0, - "_sampling_priority_v1": 1, - "openai.organization.ratelimit.requests.limit": 20, - "openai.organization.ratelimit.requests.remaining": 19, - "openai.response.usage.completion_tokens": 90, - "openai.response.usage.prompt_tokens": 28, - "openai.response.usage.total_tokens": 118, - "process_id": 7557 - }, - "duration": 5203320, - "start": 1694032213851072940 - }]] diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_fine_tune_cancel.json b/tests/snapshots/tests.contrib.openai.test_openai.test_fine_tune_cancel.json deleted file mode 100644 index 7a677e615ab..00000000000 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_fine_tune_cancel.json +++ /dev/null @@ -1,49 +0,0 @@ -[[ - { - "name": "openai.request", - "service": "", - "resource": "cancelFineTune", - "trace_id": 0, - "span_id": 1, - "parent_id": 0, - "type": "", - "error": 0, - "meta": { - "_dd.p.dm": "-0", - "_dd.p.tid": "654a694400000000", - "component": "openai", - "language": "python", - "openai.api_base": "https://api.openai.com/v1", - "openai.api_type": "open_ai", - "openai.base_url": "https://api.openai.com/v1/", - "openai.request.endpoint": "/v1/fine-tunes/*/cancel", - "openai.request.fine_tune_id": "ft-N6ggcFNqJNuREixR9ShDWzST", - "openai.request.method": "POST", - "openai.request.user": "ddtrace-test", - "openai.response.created_at": "1685663704", - "openai.response.fine_tuned_model": "None", - "openai.response.hyperparams.batch_size": "None", - "openai.response.hyperparams.learning_rate_multiplier": "None", - "openai.response.hyperparams.n_epochs": "4", - "openai.response.hyperparams.prompt_loss_weight": "0.01", - "openai.response.id": "ft-N6ggcFNqJNuREixR9ShDWzST", - "openai.response.model": "curie", - "openai.response.status": "cancelled", - "openai.response.updated_at": "1685663725", - "openai.user.api_key": "sk-...key>", - "runtime-id": "e3412e4f1afe4767a615b30fc1c0f91d" - }, - "metrics": { - "_dd.measured": 1, - "_dd.top_level": 1, - "_dd.tracer_kr": 1.0, - "_sampling_priority_v1": 1, - "openai.response.events_count": 2, - "openai.response.result_files_count": 0, - "openai.response.training_files_count": 1, - "openai.response.validation_files_count": 0, - "process_id": 7557 - }, - "duration": 5174209, - "start": 1694032216661310715 - }]] diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_fine_tune_create.json b/tests/snapshots/tests.contrib.openai.test_openai.test_fine_tune_create.json deleted file mode 100644 index 84a122d7ab5..00000000000 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_fine_tune_create.json +++ /dev/null @@ -1,59 +0,0 @@ -[[ - { - "name": "openai.request", - "service": "", - "resource": "createFineTune", - "trace_id": 0, - "span_id": 1, - "parent_id": 0, - "type": "", - "error": 0, - "meta": { - "_dd.p.dm": "-0", - "_dd.p.tid": "654a694400000000", - "component": "openai", - "language": "python", - "openai.api_base": "https://api.openai.com/v1", - "openai.api_type": "open_ai", - "openai.base_url": "https://api.openai.com/v1/", - "openai.request.batch_size": "5", - "openai.request.classification_n_classes": "NOT_GIVEN", - "openai.request.classification_positive_class": "NOT_GIVEN", - "openai.request.compute_classification_metrics": "False", - "openai.request.endpoint": "/v1/fine-tunes", - "openai.request.learning_rate_multiplier": "0.05", - "openai.request.method": "POST", - "openai.request.model": "babbage", - "openai.request.n_epochs": "4", - "openai.request.prompt_loss_weight": "0.01", - "openai.request.suffix": "dummy-fine-tune-model", - "openai.request.training_file": "file-llDq0Q9la7EBTScAowIotxxc", - "openai.request.validation_file": "NOT_GIVEN", - "openai.response.created_at": "1685662822", - "openai.response.fine_tuned_model": "None", - "openai.response.hyperparams.batch_size": "5", - "openai.response.hyperparams.learning_rate_multiplier": "0.05", - "openai.response.hyperparams.n_epochs": "4", - "openai.response.hyperparams.prompt_loss_weight": "0.01", - "openai.response.id": "ft-vDSkQPyS15HrnjmJ2Xwwb0wi", - "openai.response.model": "babbage", - "openai.response.status": "pending", - "openai.response.updated_at": "1685662822", - "openai.user.api_key": "sk-...key>", - "runtime-id": "e3412e4f1afe4767a615b30fc1c0f91d" - }, - "metrics": { - "_dd.measured": 1, - "_dd.top_level": 1, - "_dd.tracer_kr": 1.0, - "_sampling_priority_v1": 1, - "openai.request.classification_betas_count": 0, - "openai.response.events_count": 1, - "openai.response.result_files_count": 0, - "openai.response.training_files_count": 1, - "openai.response.validation_files_count": 0, - "process_id": 7557 - }, - "duration": 5288325, - "start": 1694032216363023619 - }]] diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_fine_tune_list.json b/tests/snapshots/tests.contrib.openai.test_openai.test_fine_tune_list.json deleted file mode 100644 index 102d70293e9..00000000000 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_fine_tune_list.json +++ /dev/null @@ -1,35 +0,0 @@ -[[ - { - "name": "openai.request", - "service": "", - "resource": "listFineTunes", - "trace_id": 0, - "span_id": 1, - "parent_id": 0, - "type": "", - "error": 0, - "meta": { - "_dd.p.dm": "-0", - "_dd.p.tid": "654a694400000000", - "component": "openai", - "language": "python", - "openai.api_base": "https://api.openai.com/v1", - "openai.api_type": "open_ai", - "openai.base_url": "https://api.openai.com/v1/", - "openai.request.endpoint": "/v1/fine-tunes", - "openai.request.method": "GET", - "openai.request.user": "ddtrace-test", - "openai.user.api_key": "sk-...key>", - "runtime-id": "e3412e4f1afe4767a615b30fc1c0f91d" - }, - "metrics": { - "_dd.measured": 1, - "_dd.top_level": 1, - "_dd.tracer_kr": 1.0, - "_sampling_priority_v1": 1, - "openai.response.count": 52, - "process_id": 7557 - }, - "duration": 15621250, - "start": 1694032216125710155 - }]] diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_fine_tune_list_events.json b/tests/snapshots/tests.contrib.openai.test_openai.test_fine_tune_list_events.json deleted file mode 100644 index 9794c4b950f..00000000000 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_fine_tune_list_events.json +++ /dev/null @@ -1,37 +0,0 @@ -[[ - { - "name": "openai.request", - "service": "", - "resource": "listFineTuneEvents", - "trace_id": 0, - "span_id": 1, - "parent_id": 0, - "type": "", - "error": 0, - "meta": { - "_dd.p.dm": "-0", - "_dd.p.tid": "654a694400000000", - "component": "openai", - "language": "python", - "openai.api_base": "https://api.openai.com/v1", - "openai.api_type": "open_ai", - "openai.base_url": "https://api.openai.com/v1/", - "openai.request.endpoint": "/v1/fine-tunes/*/events", - "openai.request.fine_tune_id": "ft-N6ggcFNqJNuREixR9ShDWzST", - "openai.request.method": "GET", - "openai.request.stream": "False", - "openai.request.user": "ddtrace-test", - "openai.user.api_key": "sk-...key>", - "runtime-id": "e3412e4f1afe4767a615b30fc1c0f91d" - }, - "metrics": { - "_dd.measured": 1, - "_dd.top_level": 1, - "_dd.tracer_kr": 1.0, - "_sampling_priority_v1": 1, - "openai.response.count": 2, - "process_id": 7557 - }, - "duration": 4898599, - "start": 1694032216959392128 - }]] diff --git a/tests/snapshots/tests.contrib.openai.test_openai.test_fine_tune_retrieve.json b/tests/snapshots/tests.contrib.openai.test_openai.test_fine_tune_retrieve.json deleted file mode 100644 index 18447efc479..00000000000 --- a/tests/snapshots/tests.contrib.openai.test_openai.test_fine_tune_retrieve.json +++ /dev/null @@ -1,49 +0,0 @@ -[[ - { - "name": "openai.request", - "service": "", - "resource": "retrieveFineTune", - "trace_id": 0, - "span_id": 1, - "parent_id": 0, - "type": "", - "error": 0, - "meta": { - "_dd.p.dm": "-0", - "_dd.p.tid": "654a694400000000", - "component": "openai", - "language": "python", - "openai.api_base": "https://api.openai.com/v1", - "openai.api_type": "open_ai", - "openai.base_url": "https://api.openai.com/v1/", - "openai.request.endpoint": "/v1/fine-tunes/*", - "openai.request.fine_tune_id": "ft-sADEaavxRFrjOQ65XkQKm0zM", - "openai.request.method": "GET", - "openai.request.user": "ddtrace-test", - "openai.response.created_at": "1685662960", - "openai.response.fine_tuned_model": "None", - "openai.response.hyperparams.batch_size": "5", - "openai.response.hyperparams.learning_rate_multiplier": "0.05", - "openai.response.hyperparams.n_epochs": "4", - "openai.response.hyperparams.prompt_loss_weight": "0.01", - "openai.response.id": "ft-sADEaavxRFrjOQ65XkQKm0zM", - "openai.response.model": "babbage", - "openai.response.status": "cancelled", - "openai.response.updated_at": "1685663003", - "openai.user.api_key": "sk-...key>", - "runtime-id": "e3412e4f1afe4767a615b30fc1c0f91d" - }, - "metrics": { - "_dd.measured": 1, - "_dd.top_level": 1, - "_dd.tracer_kr": 1.0, - "_sampling_priority_v1": 1, - "openai.response.events_count": 2, - "openai.response.result_files_count": 0, - "openai.response.training_files_count": 1, - "openai.response.validation_files_count": 0, - "process_id": 7557 - }, - "duration": 5101197, - "start": 1694032216511805088 - }]] From 0e8a2579af9b8d28e7c8c39ece51c506417708b0 Mon Sep 17 00:00:00 2001 From: Yun Kim <35776586+Yun-Kim@users.noreply.github.com> Date: Tue, 21 May 2024 16:44:05 -0400 Subject: [PATCH 089/104] fix(openai): wrap streamed response (#9321) Fixes #9077. This PR changes the way we patch streamed completion/chat responses in the OpenAI integration. Previously we had been returning generator functions that yielded the value of each chunk. However, this was incorrect as `OpenAIStream/OpenAIAsyncStream` objects can be used as context managers as of `openai>=1.6.0`, which means returning a generator function to replace `OpenAIStream` would break user applications that tried to access stream responses as context managers: ```python with openai.OpenAI().completions.create(..., stream=True) as response: for chunk in response: print(chunk) ``` There is no introduced/changed functionality other than the fact that we now returned a wrapped `TracedOpenAIStream` object instead of the previous traced generator function. Note: This PR may seem much larger than it really is, we also updated the tested versions of OpenAI so the riot requirement lockfiles have been changed. ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .riot/requirements/14561ae.txt | 53 ------- .riot/requirements/1642d4d.txt | 53 +++++++ .../requirements/{1ac1030.txt => 1666d46.txt} | 29 ++-- .riot/requirements/169042d.txt | 57 ++++++++ .riot/requirements/17c49c8.txt | 55 ++++++++ .riot/requirements/185da79.txt | 51 +++++++ .riot/requirements/194d473.txt | 53 +++++++ .riot/requirements/1a8fb3e.txt | 49 ------- .riot/requirements/1e5eb59.txt | 57 -------- .riot/requirements/1e7ee60.txt | 57 ++++++++ .riot/requirements/607e841.txt | 51 ------- .riot/requirements/6321007.txt | 53 ------- .riot/requirements/9ad019f.txt | 53 +++++++ .riot/requirements/d6cc29d.txt | 55 -------- .riot/requirements/e877b9f.txt | 53 ------- .riot/requirements/e9958eb.txt | 49 +++++++ .riot/requirements/f5090e1.txt | 57 -------- ddtrace/contrib/openai/_endpoint_hooks.py | 71 ++++------ ddtrace/contrib/openai/utils.py | 104 ++++++++++++++ ...i-streamed-responses-6db98c802c331b46.yaml | 5 + riotfile.py | 4 +- tests/contrib/openai/test_openai_v1.py | 129 ++++++++++++++---- 22 files changed, 688 insertions(+), 510 deletions(-) delete mode 100644 .riot/requirements/14561ae.txt create mode 100644 .riot/requirements/1642d4d.txt rename .riot/requirements/{1ac1030.txt => 1666d46.txt} (70%) create mode 100644 .riot/requirements/169042d.txt create mode 100644 .riot/requirements/17c49c8.txt create mode 100644 .riot/requirements/185da79.txt create mode 100644 .riot/requirements/194d473.txt delete mode 100644 .riot/requirements/1a8fb3e.txt delete mode 100644 .riot/requirements/1e5eb59.txt create mode 100644 .riot/requirements/1e7ee60.txt delete mode 100644 .riot/requirements/607e841.txt delete mode 100644 .riot/requirements/6321007.txt create mode 100644 .riot/requirements/9ad019f.txt delete mode 100644 .riot/requirements/d6cc29d.txt delete mode 100644 .riot/requirements/e877b9f.txt create mode 100644 .riot/requirements/e9958eb.txt delete mode 100644 .riot/requirements/f5090e1.txt create mode 100644 releasenotes/notes/fix-openai-streamed-responses-6db98c802c331b46.yaml diff --git a/.riot/requirements/14561ae.txt b/.riot/requirements/14561ae.txt deleted file mode 100644 index 9ae38519242..00000000000 --- a/.riot/requirements/14561ae.txt +++ /dev/null @@ -1,53 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/14561ae.in -# -annotated-types==0.6.0 -anyio==4.2.0 -attrs==23.1.0 -certifi==2023.11.17 -coverage[toml]==7.3.4 -distro==1.8.0 -exceptiongroup==1.2.0 -h11==0.14.0 -httpcore==1.0.2 -httpx==0.26.0 -hypothesis==6.45.0 -idna==3.6 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -multidict==6.0.4 -numpy==1.24.4 -openai[datalib,embeddings]==1.3.9 -opentracing==2.4.0 -packaging==23.2 -pandas==2.0.3 -pandas-stubs==2.0.3.230814 -pillow==10.1.0 -pluggy==1.3.0 -pydantic==2.5.3 -pydantic-core==2.14.6 -pytest==7.4.3 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -python-dateutil==2.8.2 -pytz==2023.3.post1 -pyyaml==6.0.1 -six==1.16.0 -sniffio==1.3.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -tqdm==4.66.1 -types-pytz==2023.3.1.1 -typing-extensions==4.9.0 -tzdata==2023.3 -urllib3==1.26.18 -vcrpy==4.2.1 -wrapt==1.16.0 -yarl==1.9.4 -zipp==3.17.0 diff --git a/.riot/requirements/1642d4d.txt b/.riot/requirements/1642d4d.txt new file mode 100644 index 00000000000..82c806406a6 --- /dev/null +++ b/.riot/requirements/1642d4d.txt @@ -0,0 +1,53 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/1642d4d.in +# +annotated-types==0.7.0 +anyio==4.3.0 +attrs==23.2.0 +certifi==2024.2.2 +charset-normalizer==3.3.2 +coverage[toml]==7.5.1 +distro==1.9.0 +h11==0.14.0 +httpcore==1.0.5 +httpx==0.27.0 +hypothesis==6.45.0 +idna==3.7 +iniconfig==2.0.0 +mock==5.1.0 +multidict==6.0.5 +numpy==1.26.4 +openai[datalib]==1.30.1 +opentracing==2.4.0 +packaging==24.0 +pandas==2.2.2 +pandas-stubs==2.2.2.240514 +pillow==10.3.0 +pluggy==1.5.0 +pydantic==2.7.1 +pydantic-core==2.18.2 +pytest==8.2.1 +pytest-asyncio==0.21.1 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +python-dateutil==2.9.0.post0 +pytz==2024.1 +pyyaml==6.0.1 +regex==2024.5.15 +requests==2.32.0 +six==1.16.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 +tiktoken==0.7.0 +tqdm==4.66.4 +types-pytz==2024.1.0.20240417 +typing-extensions==4.11.0 +tzdata==2024.1 +urllib3==1.26.18 +vcrpy==4.2.1 +wrapt==1.16.0 +yarl==1.9.4 diff --git a/.riot/requirements/1ac1030.txt b/.riot/requirements/1666d46.txt similarity index 70% rename from .riot/requirements/1ac1030.txt rename to .riot/requirements/1666d46.txt index 4cec9cef00d..e74f4447748 100644 --- a/.riot/requirements/1ac1030.txt +++ b/.riot/requirements/1666d46.txt @@ -2,47 +2,48 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/1ac1030.in +# pip-compile --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/1666d46.in # annotated-types==0.5.0 anyio==3.7.1 -attrs==23.1.0 -certifi==2023.11.17 +attrs==23.2.0 +cached-property==1.5.2 +certifi==2024.2.2 coverage[toml]==7.2.7 -distro==1.8.0 -exceptiongroup==1.2.0 +distro==1.9.0 +exceptiongroup==1.2.1 h11==0.14.0 httpcore==0.17.3 httpx==0.24.1 hypothesis==6.45.0 -idna==3.6 +idna==3.7 importlib-metadata==6.7.0 iniconfig==2.0.0 mock==5.1.0 -multidict==6.0.4 +multidict==6.0.5 numpy==1.21.6 -openai[datalib,embeddings]==1.3.9 +openai[datalib,embeddings]==1.30.1 opentracing==2.4.0 -packaging==23.2 +packaging==24.0 pandas==1.3.5 pandas-stubs==1.2.0.62 pillow==9.5.0 pluggy==1.2.0 pydantic==2.5.3 pydantic-core==2.14.6 -pytest==7.4.3 +pytest==7.4.4 pytest-asyncio==0.21.1 pytest-cov==4.1.0 pytest-mock==3.11.1 pytest-randomly==3.12.0 -python-dateutil==2.8.2 -pytz==2023.3.post1 +python-dateutil==2.9.0.post0 +pytz==2024.1 pyyaml==6.0.1 six==1.16.0 -sniffio==1.3.0 +sniffio==1.3.1 sortedcontainers==2.4.0 tomli==2.0.1 -tqdm==4.66.1 +tqdm==4.66.4 typing-extensions==4.7.1 urllib3==1.26.18 vcrpy==4.2.1 diff --git a/.riot/requirements/169042d.txt b/.riot/requirements/169042d.txt new file mode 100644 index 00000000000..6fd565b093f --- /dev/null +++ b/.riot/requirements/169042d.txt @@ -0,0 +1,57 @@ +# +# This file is autogenerated by pip-compile with Python 3.9 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/169042d.in +# +annotated-types==0.7.0 +anyio==4.3.0 +attrs==23.2.0 +certifi==2024.2.2 +charset-normalizer==3.3.2 +coverage[toml]==7.5.1 +distro==1.9.0 +exceptiongroup==1.2.1 +h11==0.14.0 +httpcore==1.0.5 +httpx==0.27.0 +hypothesis==6.45.0 +idna==3.7 +importlib-metadata==7.1.0 +iniconfig==2.0.0 +mock==5.1.0 +multidict==6.0.5 +numpy==1.26.4 +openai[datalib]==1.30.1 +opentracing==2.4.0 +packaging==24.0 +pandas==2.2.2 +pandas-stubs==2.2.2.240514 +pillow==10.3.0 +pluggy==1.5.0 +pydantic==2.7.1 +pydantic-core==2.18.2 +pytest==8.2.1 +pytest-asyncio==0.21.1 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +python-dateutil==2.9.0.post0 +pytz==2024.1 +pyyaml==6.0.1 +regex==2024.5.15 +requests==2.32.0 +six==1.16.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 +tiktoken==0.7.0 +tomli==2.0.1 +tqdm==4.66.4 +types-pytz==2024.1.0.20240417 +typing-extensions==4.11.0 +tzdata==2024.1 +urllib3==1.26.18 +vcrpy==4.2.1 +wrapt==1.16.0 +yarl==1.9.4 +zipp==3.18.2 diff --git a/.riot/requirements/17c49c8.txt b/.riot/requirements/17c49c8.txt new file mode 100644 index 00000000000..a199149ecce --- /dev/null +++ b/.riot/requirements/17c49c8.txt @@ -0,0 +1,55 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/17c49c8.in +# +annotated-types==0.7.0 +anyio==4.3.0 +attrs==23.2.0 +certifi==2024.2.2 +charset-normalizer==3.3.2 +coverage[toml]==7.5.1 +distro==1.9.0 +exceptiongroup==1.2.1 +h11==0.14.0 +httpcore==1.0.5 +httpx==0.27.0 +hypothesis==6.45.0 +idna==3.7 +iniconfig==2.0.0 +mock==5.1.0 +multidict==6.0.5 +numpy==1.26.4 +openai[datalib]==1.30.1 +opentracing==2.4.0 +packaging==24.0 +pandas==2.2.2 +pandas-stubs==2.2.2.240514 +pillow==10.3.0 +pluggy==1.5.0 +pydantic==2.7.1 +pydantic-core==2.18.2 +pytest==8.2.1 +pytest-asyncio==0.21.1 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +python-dateutil==2.9.0.post0 +pytz==2024.1 +pyyaml==6.0.1 +regex==2024.5.15 +requests==2.32.0 +six==1.16.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 +tiktoken==0.7.0 +tomli==2.0.1 +tqdm==4.66.4 +types-pytz==2024.1.0.20240417 +typing-extensions==4.11.0 +tzdata==2024.1 +urllib3==1.26.18 +vcrpy==4.2.1 +wrapt==1.16.0 +yarl==1.9.4 diff --git a/.riot/requirements/185da79.txt b/.riot/requirements/185da79.txt new file mode 100644 index 00000000000..b10648c8e41 --- /dev/null +++ b/.riot/requirements/185da79.txt @@ -0,0 +1,51 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/185da79.in +# +annotated-types==0.7.0 +anyio==4.3.0 +attrs==23.2.0 +certifi==2024.2.2 +coverage[toml]==7.5.1 +distro==1.9.0 +exceptiongroup==1.2.1 +h11==0.14.0 +httpcore==1.0.5 +httpx==0.27.0 +hypothesis==6.45.0 +idna==3.7 +iniconfig==2.0.0 +mock==5.1.0 +multidict==6.0.5 +numpy==1.26.4 +openai[datalib,embeddings]==1.30.1 +opentracing==2.4.0 +packaging==24.0 +pandas==2.2.2 +pandas-stubs==2.2.2.240514 +pillow==10.3.0 +pluggy==1.5.0 +pydantic==2.7.1 +pydantic-core==2.18.2 +pytest==8.2.1 +pytest-asyncio==0.21.1 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +python-dateutil==2.9.0.post0 +pytz==2024.1 +pyyaml==6.0.1 +six==1.16.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 +tomli==2.0.1 +tqdm==4.66.4 +types-pytz==2024.1.0.20240417 +typing-extensions==4.11.0 +tzdata==2024.1 +urllib3==1.26.18 +vcrpy==4.2.1 +wrapt==1.16.0 +yarl==1.9.4 diff --git a/.riot/requirements/194d473.txt b/.riot/requirements/194d473.txt new file mode 100644 index 00000000000..8b03bcc82b3 --- /dev/null +++ b/.riot/requirements/194d473.txt @@ -0,0 +1,53 @@ +# +# This file is autogenerated by pip-compile with Python 3.9 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/194d473.in +# +annotated-types==0.7.0 +anyio==4.3.0 +attrs==23.2.0 +certifi==2024.2.2 +coverage[toml]==7.5.1 +distro==1.9.0 +exceptiongroup==1.2.1 +h11==0.14.0 +httpcore==1.0.5 +httpx==0.27.0 +hypothesis==6.45.0 +idna==3.7 +importlib-metadata==7.1.0 +iniconfig==2.0.0 +mock==5.1.0 +multidict==6.0.5 +numpy==1.26.4 +openai[datalib,embeddings]==1.30.1 +opentracing==2.4.0 +packaging==24.0 +pandas==2.2.2 +pandas-stubs==2.2.2.240514 +pillow==10.3.0 +pluggy==1.5.0 +pydantic==2.7.1 +pydantic-core==2.18.2 +pytest==8.2.1 +pytest-asyncio==0.21.1 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +python-dateutil==2.9.0.post0 +pytz==2024.1 +pyyaml==6.0.1 +six==1.16.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 +tomli==2.0.1 +tqdm==4.66.4 +types-pytz==2024.1.0.20240417 +typing-extensions==4.11.0 +tzdata==2024.1 +urllib3==1.26.18 +vcrpy==4.2.1 +wrapt==1.16.0 +yarl==1.9.4 +zipp==3.18.2 diff --git a/.riot/requirements/1a8fb3e.txt b/.riot/requirements/1a8fb3e.txt deleted file mode 100644 index a022244aa4b..00000000000 --- a/.riot/requirements/1a8fb3e.txt +++ /dev/null @@ -1,49 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.11 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1a8fb3e.in -# -annotated-types==0.6.0 -anyio==4.2.0 -attrs==23.1.0 -certifi==2023.11.17 -coverage[toml]==7.3.4 -distro==1.8.0 -h11==0.14.0 -httpcore==1.0.2 -httpx==0.26.0 -hypothesis==6.45.0 -idna==3.6 -iniconfig==2.0.0 -mock==5.1.0 -multidict==6.0.4 -numpy==1.26.2 -openai[datalib,embeddings]==1.3.9 -opentracing==2.4.0 -packaging==23.2 -pandas==2.1.4 -pandas-stubs==2.1.4.231218 -pillow==10.1.0 -pluggy==1.3.0 -pydantic==2.5.3 -pydantic-core==2.14.6 -pytest==7.4.3 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -python-dateutil==2.8.2 -pytz==2023.3.post1 -pyyaml==6.0.1 -six==1.16.0 -sniffio==1.3.0 -sortedcontainers==2.4.0 -tqdm==4.66.1 -types-pytz==2023.3.1.1 -typing-extensions==4.9.0 -tzdata==2023.3 -urllib3==1.26.18 -vcrpy==4.2.1 -wrapt==1.16.0 -yarl==1.9.4 diff --git a/.riot/requirements/1e5eb59.txt b/.riot/requirements/1e5eb59.txt deleted file mode 100644 index 6f75561cd62..00000000000 --- a/.riot/requirements/1e5eb59.txt +++ /dev/null @@ -1,57 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1e5eb59.in -# -annotated-types==0.6.0 -anyio==4.2.0 -attrs==23.1.0 -certifi==2023.11.17 -charset-normalizer==3.3.2 -coverage[toml]==7.3.4 -distro==1.8.0 -exceptiongroup==1.2.0 -h11==0.14.0 -httpcore==1.0.2 -httpx==0.26.0 -hypothesis==6.45.0 -idna==3.6 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -multidict==6.0.4 -numpy==1.24.4 -openai[datalib]==1.3.9 -opentracing==2.4.0 -packaging==23.2 -pandas==2.0.3 -pandas-stubs==2.0.3.230814 -pillow==10.1.0 -pluggy==1.3.0 -pydantic==2.5.3 -pydantic-core==2.14.6 -pytest==7.4.3 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -python-dateutil==2.8.2 -pytz==2023.3.post1 -pyyaml==6.0.1 -regex==2023.10.3 -requests==2.31.0 -six==1.16.0 -sniffio==1.3.0 -sortedcontainers==2.4.0 -tiktoken==0.5.2 -tomli==2.0.1 -tqdm==4.66.1 -types-pytz==2023.3.1.1 -typing-extensions==4.9.0 -tzdata==2023.3 -urllib3==1.26.18 -vcrpy==4.2.1 -wrapt==1.16.0 -yarl==1.9.4 -zipp==3.17.0 diff --git a/.riot/requirements/1e7ee60.txt b/.riot/requirements/1e7ee60.txt new file mode 100644 index 00000000000..dcc1cf169e6 --- /dev/null +++ b/.riot/requirements/1e7ee60.txt @@ -0,0 +1,57 @@ +# +# This file is autogenerated by pip-compile with Python 3.8 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/1e7ee60.in +# +annotated-types==0.7.0 +anyio==4.3.0 +attrs==23.2.0 +certifi==2024.2.2 +charset-normalizer==3.3.2 +coverage[toml]==7.5.1 +distro==1.9.0 +exceptiongroup==1.2.1 +h11==0.14.0 +httpcore==1.0.5 +httpx==0.27.0 +hypothesis==6.45.0 +idna==3.7 +importlib-metadata==7.1.0 +iniconfig==2.0.0 +mock==5.1.0 +multidict==6.0.5 +numpy==1.24.4 +openai[datalib]==1.30.1 +opentracing==2.4.0 +packaging==24.0 +pandas==2.0.3 +pandas-stubs==2.0.3.230814 +pillow==10.3.0 +pluggy==1.5.0 +pydantic==2.7.1 +pydantic-core==2.18.2 +pytest==8.2.1 +pytest-asyncio==0.21.1 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +python-dateutil==2.9.0.post0 +pytz==2024.1 +pyyaml==6.0.1 +regex==2024.5.15 +requests==2.32.0 +six==1.16.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 +tiktoken==0.7.0 +tomli==2.0.1 +tqdm==4.66.4 +types-pytz==2024.1.0.20240417 +typing-extensions==4.11.0 +tzdata==2024.1 +urllib3==1.26.18 +vcrpy==4.2.1 +wrapt==1.16.0 +yarl==1.9.4 +zipp==3.18.2 diff --git a/.riot/requirements/607e841.txt b/.riot/requirements/607e841.txt deleted file mode 100644 index 195462351de..00000000000 --- a/.riot/requirements/607e841.txt +++ /dev/null @@ -1,51 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/607e841.in -# -annotated-types==0.6.0 -anyio==4.2.0 -attrs==23.1.0 -certifi==2023.11.17 -coverage[toml]==7.3.4 -distro==1.8.0 -exceptiongroup==1.2.0 -h11==0.14.0 -httpcore==1.0.2 -httpx==0.26.0 -hypothesis==6.45.0 -idna==3.6 -iniconfig==2.0.0 -mock==5.1.0 -multidict==6.0.4 -numpy==1.26.2 -openai[datalib,embeddings]==1.3.9 -opentracing==2.4.0 -packaging==23.2 -pandas==2.1.4 -pandas-stubs==2.1.4.231218 -pillow==10.1.0 -pluggy==1.3.0 -pydantic==2.5.3 -pydantic-core==2.14.6 -pytest==7.4.3 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -python-dateutil==2.8.2 -pytz==2023.3.post1 -pyyaml==6.0.1 -six==1.16.0 -sniffio==1.3.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -tqdm==4.66.1 -types-pytz==2023.3.1.1 -typing-extensions==4.9.0 -tzdata==2023.3 -urllib3==1.26.18 -vcrpy==4.2.1 -wrapt==1.16.0 -yarl==1.9.4 diff --git a/.riot/requirements/6321007.txt b/.riot/requirements/6321007.txt deleted file mode 100644 index 24b8b1f0d9f..00000000000 --- a/.riot/requirements/6321007.txt +++ /dev/null @@ -1,53 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.9 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/6321007.in -# -annotated-types==0.6.0 -anyio==4.2.0 -attrs==23.1.0 -certifi==2023.11.17 -coverage[toml]==7.3.4 -distro==1.8.0 -exceptiongroup==1.2.0 -h11==0.14.0 -httpcore==1.0.2 -httpx==0.26.0 -hypothesis==6.45.0 -idna==3.6 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -multidict==6.0.4 -numpy==1.26.2 -openai[datalib,embeddings]==1.3.9 -opentracing==2.4.0 -packaging==23.2 -pandas==2.1.4 -pandas-stubs==2.1.4.231218 -pillow==10.1.0 -pluggy==1.3.0 -pydantic==2.5.3 -pydantic-core==2.14.6 -pytest==7.4.3 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -python-dateutil==2.8.2 -pytz==2023.3.post1 -pyyaml==6.0.1 -six==1.16.0 -sniffio==1.3.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -tqdm==4.66.1 -types-pytz==2023.3.1.1 -typing-extensions==4.9.0 -tzdata==2023.3 -urllib3==1.26.18 -vcrpy==4.2.1 -wrapt==1.16.0 -yarl==1.9.4 -zipp==3.17.0 diff --git a/.riot/requirements/9ad019f.txt b/.riot/requirements/9ad019f.txt new file mode 100644 index 00000000000..f0c802e7ba1 --- /dev/null +++ b/.riot/requirements/9ad019f.txt @@ -0,0 +1,53 @@ +# +# This file is autogenerated by pip-compile with Python 3.8 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/9ad019f.in +# +annotated-types==0.7.0 +anyio==4.3.0 +attrs==23.2.0 +certifi==2024.2.2 +coverage[toml]==7.5.1 +distro==1.9.0 +exceptiongroup==1.2.1 +h11==0.14.0 +httpcore==1.0.5 +httpx==0.27.0 +hypothesis==6.45.0 +idna==3.7 +importlib-metadata==7.1.0 +iniconfig==2.0.0 +mock==5.1.0 +multidict==6.0.5 +numpy==1.24.4 +openai[datalib,embeddings]==1.30.1 +opentracing==2.4.0 +packaging==24.0 +pandas==2.0.3 +pandas-stubs==2.0.3.230814 +pillow==10.3.0 +pluggy==1.5.0 +pydantic==2.7.1 +pydantic-core==2.18.2 +pytest==8.2.1 +pytest-asyncio==0.21.1 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +python-dateutil==2.9.0.post0 +pytz==2024.1 +pyyaml==6.0.1 +six==1.16.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 +tomli==2.0.1 +tqdm==4.66.4 +types-pytz==2024.1.0.20240417 +typing-extensions==4.11.0 +tzdata==2024.1 +urllib3==1.26.18 +vcrpy==4.2.1 +wrapt==1.16.0 +yarl==1.9.4 +zipp==3.18.2 diff --git a/.riot/requirements/d6cc29d.txt b/.riot/requirements/d6cc29d.txt deleted file mode 100644 index 37a6301eeab..00000000000 --- a/.riot/requirements/d6cc29d.txt +++ /dev/null @@ -1,55 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/d6cc29d.in -# -annotated-types==0.6.0 -anyio==4.2.0 -attrs==23.1.0 -certifi==2023.11.17 -charset-normalizer==3.3.2 -coverage[toml]==7.3.4 -distro==1.8.0 -exceptiongroup==1.2.0 -h11==0.14.0 -httpcore==1.0.2 -httpx==0.26.0 -hypothesis==6.45.0 -idna==3.6 -iniconfig==2.0.0 -mock==5.1.0 -multidict==6.0.4 -numpy==1.26.2 -openai[datalib]==1.3.9 -opentracing==2.4.0 -packaging==23.2 -pandas==2.1.4 -pandas-stubs==2.1.4.231218 -pillow==10.1.0 -pluggy==1.3.0 -pydantic==2.5.3 -pydantic-core==2.14.6 -pytest==7.4.3 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -python-dateutil==2.8.2 -pytz==2023.3.post1 -pyyaml==6.0.1 -regex==2023.10.3 -requests==2.31.0 -six==1.16.0 -sniffio==1.3.0 -sortedcontainers==2.4.0 -tiktoken==0.5.2 -tomli==2.0.1 -tqdm==4.66.1 -types-pytz==2023.3.1.1 -typing-extensions==4.9.0 -tzdata==2023.3 -urllib3==1.26.18 -vcrpy==4.2.1 -wrapt==1.16.0 -yarl==1.9.4 diff --git a/.riot/requirements/e877b9f.txt b/.riot/requirements/e877b9f.txt deleted file mode 100644 index db564b33dcc..00000000000 --- a/.riot/requirements/e877b9f.txt +++ /dev/null @@ -1,53 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.11 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/e877b9f.in -# -annotated-types==0.6.0 -anyio==4.2.0 -attrs==23.1.0 -certifi==2023.11.17 -charset-normalizer==3.3.2 -coverage[toml]==7.3.4 -distro==1.8.0 -h11==0.14.0 -httpcore==1.0.2 -httpx==0.26.0 -hypothesis==6.45.0 -idna==3.6 -iniconfig==2.0.0 -mock==5.1.0 -multidict==6.0.4 -numpy==1.26.2 -openai[datalib]==1.3.9 -opentracing==2.4.0 -packaging==23.2 -pandas==2.1.4 -pandas-stubs==2.1.4.231218 -pillow==10.1.0 -pluggy==1.3.0 -pydantic==2.5.3 -pydantic-core==2.14.6 -pytest==7.4.3 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -python-dateutil==2.8.2 -pytz==2023.3.post1 -pyyaml==6.0.1 -regex==2023.10.3 -requests==2.31.0 -six==1.16.0 -sniffio==1.3.0 -sortedcontainers==2.4.0 -tiktoken==0.5.2 -tqdm==4.66.1 -types-pytz==2023.3.1.1 -typing-extensions==4.9.0 -tzdata==2023.3 -urllib3==1.26.18 -vcrpy==4.2.1 -wrapt==1.16.0 -yarl==1.9.4 diff --git a/.riot/requirements/e9958eb.txt b/.riot/requirements/e9958eb.txt new file mode 100644 index 00000000000..6d04bb99ccc --- /dev/null +++ b/.riot/requirements/e9958eb.txt @@ -0,0 +1,49 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --no-annotate .riot/requirements/e9958eb.in +# +annotated-types==0.7.0 +anyio==4.3.0 +attrs==23.2.0 +certifi==2024.2.2 +coverage[toml]==7.5.1 +distro==1.9.0 +h11==0.14.0 +httpcore==1.0.5 +httpx==0.27.0 +hypothesis==6.45.0 +idna==3.7 +iniconfig==2.0.0 +mock==5.1.0 +multidict==6.0.5 +numpy==1.26.4 +openai[datalib,embeddings]==1.30.1 +opentracing==2.4.0 +packaging==24.0 +pandas==2.2.2 +pandas-stubs==2.2.2.240514 +pillow==10.3.0 +pluggy==1.5.0 +pydantic==2.7.1 +pydantic-core==2.18.2 +pytest==8.2.1 +pytest-asyncio==0.21.1 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +python-dateutil==2.9.0.post0 +pytz==2024.1 +pyyaml==6.0.1 +six==1.16.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 +tqdm==4.66.4 +types-pytz==2024.1.0.20240417 +typing-extensions==4.11.0 +tzdata==2024.1 +urllib3==1.26.18 +vcrpy==4.2.1 +wrapt==1.16.0 +yarl==1.9.4 diff --git a/.riot/requirements/f5090e1.txt b/.riot/requirements/f5090e1.txt deleted file mode 100644 index da425c13ae4..00000000000 --- a/.riot/requirements/f5090e1.txt +++ /dev/null @@ -1,57 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.9 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/f5090e1.in -# -annotated-types==0.6.0 -anyio==4.2.0 -attrs==23.1.0 -certifi==2023.11.17 -charset-normalizer==3.3.2 -coverage[toml]==7.3.4 -distro==1.8.0 -exceptiongroup==1.2.0 -h11==0.14.0 -httpcore==1.0.2 -httpx==0.26.0 -hypothesis==6.45.0 -idna==3.6 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -multidict==6.0.4 -numpy==1.26.2 -openai[datalib]==1.3.9 -opentracing==2.4.0 -packaging==23.2 -pandas==2.1.4 -pandas-stubs==2.1.4.231218 -pillow==10.1.0 -pluggy==1.3.0 -pydantic==2.5.3 -pydantic-core==2.14.6 -pytest==7.4.3 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -python-dateutil==2.8.2 -pytz==2023.3.post1 -pyyaml==6.0.1 -regex==2023.10.3 -requests==2.31.0 -six==1.16.0 -sniffio==1.3.0 -sortedcontainers==2.4.0 -tiktoken==0.5.2 -tomli==2.0.1 -tqdm==4.66.1 -types-pytz==2023.3.1.1 -typing-extensions==4.9.0 -tzdata==2023.3 -urllib3==1.26.18 -vcrpy==4.2.1 -wrapt==1.16.0 -yarl==1.9.4 -zipp==3.17.0 diff --git a/ddtrace/contrib/openai/_endpoint_hooks.py b/ddtrace/contrib/openai/_endpoint_hooks.py index fff7e029901..2eb9992edb2 100644 --- a/ddtrace/contrib/openai/_endpoint_hooks.py +++ b/ddtrace/contrib/openai/_endpoint_hooks.py @@ -1,16 +1,16 @@ +from openai.version import VERSION as OPENAI_VERSION + +from ddtrace.contrib.openai.utils import TracedOpenAIAsyncStream +from ddtrace.contrib.openai.utils import TracedOpenAIStream +from ddtrace.contrib.openai.utils import _format_openai_api_key +from ddtrace.contrib.openai.utils import _is_async_generator +from ddtrace.contrib.openai.utils import _is_generator +from ddtrace.contrib.openai.utils import _loop_handler +from ddtrace.contrib.openai.utils import _process_finished_stream +from ddtrace.contrib.openai.utils import _tag_tool_calls from ddtrace.ext import SpanTypes - -from .utils import _construct_completion_from_streamed_chunks -from .utils import _construct_message_from_streamed_chunks -from .utils import _format_openai_api_key -from .utils import _is_async_generator -from .utils import _is_generator -from .utils import _loop_handler -from .utils import _set_metrics_on_request -from .utils import _set_metrics_on_streamed_response -from .utils import _tag_streamed_chat_completion_response -from .utils import _tag_streamed_completion_response -from .utils import _tag_tool_calls +from ddtrace.internal.utils.version import parse_version +from ddtrace.llmobs._constants import SPAN_KIND API_VERSION = "v1" @@ -96,37 +96,23 @@ def _record_response(self, pin, integration, span, args, kwargs, resp, error): class _BaseCompletionHook(_EndpointHook): _request_arg_params = ("api_key", "api_base", "api_type", "request_id", "api_version", "organization") - def _handle_streamed_response(self, integration, span, kwargs, resp, operation_id): - """Handle streamed response objects returned from endpoint calls. + def _handle_streamed_response(self, integration, span, kwargs, resp, is_completion=False): + """Handle streamed response objects returned from completions/chat endpoint calls. - This method helps with streamed responses by wrapping the generator returned with a - generator that traces the reading of the response. + This method returns a wrapped version of the OpenAIStream/OpenAIAsyncStream objects + to trace the response while it is read by the user. """ + if parse_version(OPENAI_VERSION) >= (1, 6, 0): + if _is_async_generator(resp): + return TracedOpenAIAsyncStream(resp, integration, span, kwargs, is_completion) + elif _is_generator(resp): + return TracedOpenAIStream(resp, integration, span, kwargs, is_completion) def shared_gen(): - completions = None - messages = None try: streamed_chunks = yield - _set_metrics_on_request( - integration, span, kwargs, prompts=kwargs.get("prompt", None), messages=kwargs.get("messages", None) - ) - if operation_id == _CompletionHook.OPERATION_ID: - completions = [_construct_completion_from_streamed_chunks(choice) for choice in streamed_chunks] - else: - messages = [_construct_message_from_streamed_chunks(choice) for choice in streamed_chunks] - _set_metrics_on_streamed_response(integration, span, completions=completions, messages=messages) + _process_finished_stream(integration, span, kwargs, streamed_chunks, is_completion=is_completion) finally: - if operation_id == _CompletionHook.OPERATION_ID: - if integration.is_pc_sampled_span(span): - _tag_streamed_completion_response(integration, span, completions) - if integration.is_pc_sampled_llmobs(span): - integration.llmobs_set_tags("completion", resp, span, kwargs, streamed_completions=completions) - else: - if integration.is_pc_sampled_span(span): - _tag_streamed_chat_completion_response(integration, span, messages) - if integration.is_pc_sampled_llmobs(span): - integration.llmobs_set_tags("chat", resp, span, kwargs, streamed_completions=messages) span.finish() integration.metric(span, "dist", "request.duration", span.duration_ns) @@ -136,7 +122,7 @@ async def traced_streamed_response(): g = shared_gen() g.send(None) n = kwargs.get("n", 1) or 1 - if operation_id == _CompletionHook.OPERATION_ID: + if is_completion: prompts = kwargs.get("prompt", "") if isinstance(prompts, list) and not isinstance(prompts[0], int): n *= len(prompts) @@ -159,7 +145,7 @@ def traced_streamed_response(): g = shared_gen() g.send(None) n = kwargs.get("n", 1) or 1 - if operation_id == _CompletionHook.OPERATION_ID: + if is_completion: prompts = kwargs.get("prompt", "") if isinstance(prompts, list) and not isinstance(prompts[0], int): n *= len(prompts) @@ -175,7 +161,6 @@ def traced_streamed_response(): pass return traced_streamed_response() - return resp @@ -206,6 +191,8 @@ class _CompletionHook(_BaseCompletionHook): def _record_request(self, pin, integration, span, args, kwargs): super()._record_request(pin, integration, span, args, kwargs) span.span_type = SpanTypes.LLM + if integration.is_pc_sampled_llmobs(span): + span.set_tag_str(SPAN_KIND, "llm") if integration.is_pc_sampled_span(span): prompt = kwargs.get("prompt", "") if isinstance(prompt, str): @@ -216,7 +203,7 @@ def _record_request(self, pin, integration, span, args, kwargs): def _record_response(self, pin, integration, span, args, kwargs, resp, error): resp = super()._record_response(pin, integration, span, args, kwargs, resp, error) if kwargs.get("stream") and error is None: - return self._handle_streamed_response(integration, span, kwargs, resp, operation_id=self.OPERATION_ID) + return self._handle_streamed_response(integration, span, kwargs, resp, is_completion=True) if integration.is_pc_sampled_log(span): attrs_dict = {"prompt": kwargs.get("prompt", "")} if error is None: @@ -263,6 +250,8 @@ class _ChatCompletionHook(_BaseCompletionHook): def _record_request(self, pin, integration, span, args, kwargs): super()._record_request(pin, integration, span, args, kwargs) span.span_type = SpanTypes.LLM + if integration.is_pc_sampled_llmobs(span): + span.set_tag_str(SPAN_KIND, "llm") for idx, m in enumerate(kwargs.get("messages", [])): role = getattr(m, "role", "") name = getattr(m, "name", "") @@ -279,7 +268,7 @@ def _record_request(self, pin, integration, span, args, kwargs): def _record_response(self, pin, integration, span, args, kwargs, resp, error): resp = super()._record_response(pin, integration, span, args, kwargs, resp, error) if kwargs.get("stream") and error is None: - return self._handle_streamed_response(integration, span, kwargs, resp, operation_id=self.OPERATION_ID) + return self._handle_streamed_response(integration, span, kwargs, resp, is_completion=False) if integration.is_pc_sampled_log(span): log_choices = resp.choices if hasattr(resp.choices[0], "model_dump"): diff --git a/ddtrace/contrib/openai/utils.py b/ddtrace/contrib/openai/utils.py index 749e2c8f732..53566a4336f 100644 --- a/ddtrace/contrib/openai/utils.py +++ b/ddtrace/contrib/openai/utils.py @@ -1,4 +1,5 @@ import re +import sys from typing import Any from typing import AsyncGenerator from typing import Dict @@ -6,6 +7,7 @@ from typing import List from ddtrace.internal.logger import get_logger +from ddtrace.vendor import wrapt try: @@ -21,6 +23,108 @@ _punc_regex = re.compile(r"[\w']+|[.,!?;~@#$%^&*()+/-]") +def _process_finished_stream(integration, span, kwargs, streamed_chunks, is_completion=False): + completions, messages = None, None + prompts = kwargs.get("prompt", None) + messages = kwargs.get("messages", None) + try: + _set_metrics_on_request(integration, span, kwargs, prompts=prompts, messages=messages) + if is_completion: + completions = [_construct_completion_from_streamed_chunks(choice) for choice in streamed_chunks] + if integration.is_pc_sampled_span(span): + _tag_streamed_completion_response(integration, span, completions) + else: + messages = [_construct_message_from_streamed_chunks(choice) for choice in streamed_chunks] + if integration.is_pc_sampled_span(span): + _tag_streamed_chat_completion_response(integration, span, messages) + _set_metrics_on_streamed_response(integration, span, completions=completions, messages=messages) + if integration.is_pc_sampled_llmobs(span): + integration.llmobs_set_tags( + "completion" if is_completion else "chat", + None, + span, + kwargs, + streamed_completions=completions if is_completion else messages, + ) + except Exception: + log.warning("Error processing streamed completion/chat response.", exc_info=True) + + +class BaseTracedOpenAIStream(wrapt.ObjectProxy): + def __init__(self, wrapped, integration, span, kwargs, is_completion=False): + super().__init__(wrapped) + n = kwargs.get("n", 1) or 1 + if is_completion: + prompts = kwargs.get("prompt", "") + if isinstance(prompts, list) and not isinstance(prompts[0], int): + n *= len(prompts) + self._dd_span = span + self._streamed_chunks = [[] for _ in range(n)] + self._dd_integration = integration + self._is_completion = is_completion + self._kwargs = kwargs + + +class TracedOpenAIStream(BaseTracedOpenAIStream): + def __enter__(self): + self.__wrapped__.__enter__() + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.__wrapped__.__exit__(exc_type, exc_val, exc_tb) + + def __iter__(self): + return self + + def __next__(self): + try: + chunk = self.__wrapped__.__next__() + _loop_handler(self._dd_span, chunk, self._streamed_chunks) + return chunk + except StopIteration: + _process_finished_stream( + self._dd_integration, self._dd_span, self._kwargs, self._streamed_chunks, self._is_completion + ) + self._dd_span.finish() + self._dd_integration.metric(self._dd_span, "dist", "request.duration", self._dd_span.duration_ns) + raise + except Exception: + self._dd_span.set_exc_info(*sys.exc_info()) + self._dd_span.finish() + self._dd_integration.metric(self._dd_span, "dist", "request.duration", self._dd_span.duration_ns) + raise + + +class TracedOpenAIAsyncStream(BaseTracedOpenAIStream): + async def __aenter__(self): + await self.__wrapped__.__aenter__() + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + await self.__wrapped__.__aexit__(exc_type, exc_val, exc_tb) + + def __aiter__(self): + return self + + async def __anext__(self): + try: + chunk = await self.__wrapped__.__anext__() + _loop_handler(self._dd_span, chunk, self._streamed_chunks) + return chunk + except StopAsyncIteration: + _process_finished_stream( + self._dd_integration, self._dd_span, self._kwargs, self._streamed_chunks, self._is_completion + ) + self._dd_span.finish() + self._dd_integration.metric(self._dd_span, "dist", "request.duration", self._dd_span.duration_ns) + raise + except Exception: + self._dd_span.set_exc_info(*sys.exc_info()) + self._dd_span.finish() + self._dd_integration.metric(self._dd_span, "dist", "request.duration", self._dd_span.duration_ns) + raise + + def _compute_token_count(content, model): # type: (Union[str, List[int]], Optional[str]) -> Tuple[bool, int] """ diff --git a/releasenotes/notes/fix-openai-streamed-responses-6db98c802c331b46.yaml b/releasenotes/notes/fix-openai-streamed-responses-6db98c802c331b46.yaml new file mode 100644 index 00000000000..28dbc511b22 --- /dev/null +++ b/releasenotes/notes/fix-openai-streamed-responses-6db98c802c331b46.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + openai: This fix resolves an issue where streamed OpenAI responses raised errors when being used as context managers. + diff --git a/riotfile.py b/riotfile.py index 4d2b39c307e..7b891582aab 100644 --- a/riotfile.py +++ b/riotfile.py @@ -2308,13 +2308,13 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): Venv( pys=select_pys(min_version="3.7", max_version="3.11"), pkgs={ - "openai[embeddings,datalib]": ["==0.27.2", "==1.1.1", "==1.3.9"], + "openai[embeddings,datalib]": ["==0.27.2", "==1.1.1", "==1.30.1"], }, ), Venv( pys=select_pys(min_version="3.8", max_version="3.11"), pkgs={ - "openai[datalib]": ["==1.3.9"], + "openai[datalib]": ["==1.30.1"], "tiktoken": latest, }, env={"TIKTOKEN_AVAILABLE": "True"}, diff --git a/tests/contrib/openai/test_openai_v1.py b/tests/contrib/openai/test_openai_v1.py index b5383222efa..9f7701d5306 100644 --- a/tests/contrib/openai/test_openai_v1.py +++ b/tests/contrib/openai/test_openai_v1.py @@ -1,6 +1,4 @@ import os -from typing import AsyncGenerator -from typing import Generator import mock import openai as openai_module @@ -963,7 +961,6 @@ def test_completion_stream(openai, openai_vcr, mock_metrics, mock_tracer): expected_completion = '! ... A page layouts page drawer? ... Interesting. The "Tools" is' client = openai.OpenAI() resp = client.completions.create(model="ada", prompt="Hello world", stream=True, n=None) - assert isinstance(resp, Generator) chunks = [c for c in resp] completion = "".join([c.choices[0].text for c in chunks]) @@ -1003,7 +1000,6 @@ async def test_completion_async_stream(openai, openai_vcr, mock_metrics, mock_tr expected_completion = '! ... A page layouts page drawer? ... Interesting. The "Tools" is' client = openai.AsyncOpenAI() resp = await client.completions.create(model="ada", prompt="Hello world", stream=True) - assert isinstance(resp, AsyncGenerator) chunks = [c async for c in resp] completion = "".join([c.choices[0].text for c in chunks]) @@ -1035,6 +1031,48 @@ async def test_completion_async_stream(openai, openai_vcr, mock_metrics, mock_tr assert mock.call.distribution("tokens.total", mock.ANY, tags=expected_tags) in mock_metrics.mock_calls +@pytest.mark.skipif( + parse_version(openai_module.version.VERSION) < (1, 6, 0), + reason="Streamed response context managers are only available v1.6.0+", +) +def test_completion_stream_context_manager(openai, openai_vcr, mock_metrics, mock_tracer): + with openai_vcr.use_cassette("completion_streamed.yaml"): + with mock.patch("ddtrace.contrib.openai.utils.encoding_for_model", create=True) as mock_encoding: + mock_encoding.return_value.encode.side_effect = lambda x: [1, 2] + expected_completion = '! ... A page layouts page drawer? ... Interesting. The "Tools" is' + client = openai.OpenAI() + with client.completions.create(model="ada", prompt="Hello world", stream=True, n=None) as resp: + chunks = [c for c in resp] + + completion = "".join([c.choices[0].text for c in chunks]) + assert completion == expected_completion + + traces = mock_tracer.pop_traces() + assert len(traces) == 1 + assert len(traces[0]) == 1 + assert traces[0][0].get_tag("openai.response.choices.0.text") == expected_completion + assert traces[0][0].get_tag("openai.response.choices.0.finish_reason") == "length" + + expected_tags = [ + "version:", + "env:", + "service:", + "openai.request.model:ada", + "openai.request.endpoint:/v1/completions", + "openai.request.method:POST", + "openai.organization.id:", + "openai.organization.name:datadog-4", + "openai.user.api_key:sk-...key>", + "error:0", + "openai.estimated:true", + ] + if TIKTOKEN_AVAILABLE: + expected_tags = expected_tags[:-1] + assert mock.call.distribution("tokens.prompt", 2, tags=expected_tags) in mock_metrics.mock_calls + assert mock.call.distribution("tokens.completion", mock.ANY, tags=expected_tags) in mock_metrics.mock_calls + assert mock.call.distribution("tokens.total", mock.ANY, tags=expected_tags) in mock_metrics.mock_calls + + def test_chat_completion_stream(openai, openai_vcr, mock_metrics, snapshot_tracer): with openai_vcr.use_cassette("chat_completion_streamed.yaml"): with mock.patch("ddtrace.contrib.openai.utils.encoding_for_model", create=True) as mock_encoding: @@ -1050,7 +1088,6 @@ def test_chat_completion_stream(openai, openai_vcr, mock_metrics, snapshot_trace user="ddtrace-test", n=None, ) - assert isinstance(resp, Generator) prompt_tokens = 8 span = snapshot_tracer.current_span() chunks = [c for c in resp] @@ -1100,7 +1137,6 @@ async def test_chat_completion_async_stream(openai, openai_vcr, mock_metrics, sn stream=True, user="ddtrace-test", ) - assert isinstance(resp, AsyncGenerator) prompt_tokens = 8 span = snapshot_tracer.current_span() chunks = [c async for c in resp] @@ -1135,6 +1171,62 @@ async def test_chat_completion_async_stream(openai, openai_vcr, mock_metrics, sn assert mock.call.distribution("tokens.total", mock.ANY, tags=expected_tags) in mock_metrics.mock_calls +@pytest.mark.skipif( + parse_version(openai_module.version.VERSION) < (1, 6, 0), + reason="Streamed response context managers are only available v1.6.0+", +) +@pytest.mark.asyncio +async def test_chat_completion_async_stream_context_manager(openai, openai_vcr, mock_metrics, snapshot_tracer): + with openai_vcr.use_cassette("chat_completion_streamed.yaml"): + with mock.patch("ddtrace.contrib.openai.utils.encoding_for_model", create=True) as mock_encoding: + mock_encoding.return_value.encode.side_effect = lambda x: [1, 2, 3, 4, 5, 6, 7, 8] + expected_completion = "The Los Angeles Dodgers won the World Series in 2020." + client = openai.AsyncOpenAI() + async with await client.chat.completions.create( + model="gpt-3.5-turbo", + messages=[ + {"role": "user", "content": "Who won the world series in 2020?"}, + ], + stream=True, + user="ddtrace-test", + n=None, + ) as resp: + prompt_tokens = 8 + span = snapshot_tracer.current_span() + chunks = [c async for c in resp] + assert len(chunks) == 15 + completion = "".join( + [c.choices[0].delta.content for c in chunks if c.choices[0].delta.content is not None] + ) + assert completion == expected_completion + + assert span.get_tag("openai.response.choices.0.message.content") == expected_completion + assert span.get_tag("openai.response.choices.0.message.role") == "assistant" + assert span.get_tag("openai.response.choices.0.finish_reason") == "stop" + + expected_tags = [ + "version:", + "env:", + "service:", + "openai.request.model:gpt-3.5-turbo", + "openai.request.endpoint:/v1/chat/completions", + "openai.request.method:POST", + "openai.organization.id:", + "openai.organization.name:datadog-4", + "openai.user.api_key:sk-...key>", + "error:0", + ] + assert mock.call.distribution("request.duration", span.duration_ns, tags=expected_tags) in mock_metrics.mock_calls + assert mock.call.gauge("ratelimit.requests", 3000, tags=expected_tags) in mock_metrics.mock_calls + assert mock.call.gauge("ratelimit.remaining.requests", 2999, tags=expected_tags) in mock_metrics.mock_calls + expected_tags += ["openai.estimated:true"] + if TIKTOKEN_AVAILABLE: + expected_tags = expected_tags[:-1] + assert mock.call.distribution("tokens.prompt", prompt_tokens, tags=expected_tags) in mock_metrics.mock_calls + assert mock.call.distribution("tokens.completion", mock.ANY, tags=expected_tags) in mock_metrics.mock_calls + assert mock.call.distribution("tokens.total", mock.ANY, tags=expected_tags) in mock_metrics.mock_calls + + @pytest.mark.snapshot( token="tests.contrib.openai.test_openai_v1.test_integration_sync", ignores=["meta.http.useragent"], async_mode=False ) @@ -1318,25 +1410,12 @@ def test_completion_truncation(openai, openai_vcr, mock_tracer, ddtrace_config_o {"role": "user", "content": "Count from 1 to 100"}, ], ) - assert resp.choices[0].model_dump() == { - "finish_reason": "stop", - "index": 0, - "message": { - "content": "1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, " - "16, 17, 18, 19, 20, " - "21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, " - "34, 35, 36, 37, 38, 39, 40, " - "41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, " - "54, 55, 56, 57, 58, 59, 60, " - "61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, " - "74, 75, 76, 77, 78, 79, 80, " - "81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, " - "94, 95, 96, 97, 98, 99, 100", - "role": "assistant", - "function_call": None, - "tool_calls": None, - }, - } + assert resp.choices[0].message.content == ( + "1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29," + " 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55," + " 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81," + " 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100" + ) traces = mock_tracer.pop_traces() assert len(traces) == 2 From d7e21b13e75488a6ca15c113cb0ca5d3fa1a7172 Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Tue, 21 May 2024 17:26:11 -0400 Subject: [PATCH 090/104] chore(ci): update profiling overhead jobs (#9329) - Ensure we run the test suites when we change the config for the test suite. - Update them all to be Python 3.10 for consistency - Updated downstream `django-simple` app to update `django-allauth` which was failing to install. ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [ ] Title is accurate - [ ] All changes are related to the pull request's stated goal - [ ] Description motivates each change - [ ] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [ ] Testing strategy adequately addresses listed risks - [ ] Change is maintainable (easy to change, telemetry, documentation) - [ ] Release note makes sense to a user of the library - [ ] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [ ] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .github/workflows/django-overhead-profile.yml | 3 ++- .github/workflows/encoders-profile.yml | 3 ++- .github/workflows/flask-overhead-profile.yml | 1 + scripts/profiles/django-simple/setup.sh | 4 ++-- scripts/profiles/encoders/setup.sh | 2 +- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/django-overhead-profile.yml b/.github/workflows/django-overhead-profile.yml index f4e52f42874..602d1302976 100644 --- a/.github/workflows/django-overhead-profile.yml +++ b/.github/workflows/django-overhead-profile.yml @@ -7,6 +7,7 @@ on: paths: - 'ddtrace/**' - 'scripts/profiles/django-simple/**' + - '.github/workflows/django-overhead-profile.yml' jobs: django-overhead-profile: runs-on: ubuntu-latest @@ -22,7 +23,7 @@ jobs: - uses: actions/setup-python@v5 with: - python-version: "3.8" + python-version: "3.10" - name: Setup run: | diff --git a/.github/workflows/encoders-profile.yml b/.github/workflows/encoders-profile.yml index 5b2d5d4483c..d95612b43c7 100644 --- a/.github/workflows/encoders-profile.yml +++ b/.github/workflows/encoders-profile.yml @@ -7,6 +7,7 @@ on: paths: - 'ddtrace/internal/_encoding.pyx' - 'scripts/profiles/encoders/**' + - '.github/workflows/encoders-profile.yml' jobs: encoders-profile: runs-on: ubuntu-latest @@ -22,7 +23,7 @@ jobs: - uses: actions/setup-python@v5 with: - python-version: "3.8" + python-version: "3.10" - name: Setup run: | diff --git a/.github/workflows/flask-overhead-profile.yml b/.github/workflows/flask-overhead-profile.yml index 2d6b6a61de7..4b9d6117701 100644 --- a/.github/workflows/flask-overhead-profile.yml +++ b/.github/workflows/flask-overhead-profile.yml @@ -7,6 +7,7 @@ on: paths: - 'ddtrace/**' - 'scripts/profiles/flask-simple/**' + - '.github/workflows/flask-overhead-profile.yml' jobs: flask-overhead-profile: runs-on: ubuntu-latest diff --git a/scripts/profiles/django-simple/setup.sh b/scripts/profiles/django-simple/setup.sh index c26477bc40e..cc6c20e9a03 100755 --- a/scripts/profiles/django-simple/setup.sh +++ b/scripts/profiles/django-simple/setup.sh @@ -21,14 +21,14 @@ then fi # Create and activate the virtualenv -python3.8 -m venv ${PREFIX} +python3.10 -m venv ${PREFIX} source ${PREFIX}/bin/activate pip install pip --upgrade # Install the application git clone https://github.com/DataDog/trace-examples.git ${PREFIX}/trace-examples pushd ${PREFIX}/trace-examples/ - git checkout 760deae1fd2f2371cf813d3ff3ca9f0e040e8c60 + git checkout origin/django-simple pushd python/django/django-simple pip install -r requirements/production.txt python manage.py migrate diff --git a/scripts/profiles/encoders/setup.sh b/scripts/profiles/encoders/setup.sh index f6ddb1b02e9..199ae65e415 100755 --- a/scripts/profiles/encoders/setup.sh +++ b/scripts/profiles/encoders/setup.sh @@ -14,7 +14,7 @@ PREFIX=${1} test -d $PREFIX && rm -rf $PREFIX || mkdir -p $PREFIX # Create and activate the virtualenv -python3.8 -m venv ${PREFIX} +python3.10 -m venv ${PREFIX} source ${PREFIX}/bin/activate pip install pip --upgrade From 2008dd7dbd7f6a726cf7d69b5e5d3e49bbb0cf8b Mon Sep 17 00:00:00 2001 From: Emmett Butler <723615+emmettbutler@users.noreply.github.com> Date: Tue, 21 May 2024 15:16:48 -0700 Subject: [PATCH 091/104] chore(ci_visibility): revert default tracer change (#9328) This reverts commit 6afcedbad8e7bc18c7b618f3660945636394898f due to reliable failures in dogweb CI ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [ ] Title is accurate - [ ] All changes are related to the pull request's stated goal - [ ] Description motivates each change - [ ] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [ ] Testing strategy adequately addresses listed risks - [ ] Change is maintainable (easy to change, telemetry, documentation) - [ ] Release note makes sense to a user of the library - [ ] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [ ] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../requirements/{d03449e.txt => 117f119.txt} | 22 +- .../requirements/{48eb599.txt => 1254841.txt} | 19 +- .../requirements/{17cf97e.txt => 14d5757.txt} | 19 +- .../requirements/{18147c2.txt => 17ecd2b.txt} | 21 +- .../requirements/{a0aa271.txt => 18cd4dd.txt} | 11 +- .../requirements/{10b8fc3.txt => 1ceca0e.txt} | 19 +- .../requirements/{1252d0f.txt => 1e0c0d6.txt} | 21 +- .../requirements/{14ca37f.txt => 1e49987.txt} | 21 +- .../requirements/{1becf29.txt => 1e67852.txt} | 20 +- .../requirements/{1163993.txt => 2a81450.txt} | 19 +- .../requirements/{3af9e27.txt => 32540b6.txt} | 22 +- .../requirements/{8fd4efc.txt => 3e6ea76.txt} | 21 +- .../requirements/{91bec06.txt => 6710b57.txt} | 21 +- .../requirements/{1b09cab.txt => 764e316.txt} | 20 +- .../requirements/{1f42cb3.txt => 77724eb.txt} | 20 +- .../requirements/{11bda89.txt => b908e36.txt} | 11 +- .../requirements/{7800b91.txt => c426f16.txt} | 12 +- .../requirements/{144615f.txt => cde42be.txt} | 22 +- ddtrace/internal/ci_visibility/filters.py | 1 + ddtrace/internal/ci_visibility/recorder.py | 3 +- ...y-use_default_tracer-8523fd1859dea0da.yaml | 4 - riotfile.py | 2 - tests/ci_visibility/test_ci_visibility.py | 1 + tests/contrib/pytest/test_pytest_snapshot.py | 26 --- ...ot.test_pytest_with_ddtrace_patch_all.json | 215 ------------------ 25 files changed, 104 insertions(+), 489 deletions(-) rename .riot/requirements/{d03449e.txt => 117f119.txt} (51%) rename .riot/requirements/{48eb599.txt => 1254841.txt} (53%) rename .riot/requirements/{17cf97e.txt => 14d5757.txt} (53%) rename .riot/requirements/{18147c2.txt => 17ecd2b.txt} (51%) rename .riot/requirements/{a0aa271.txt => 18cd4dd.txt} (69%) rename .riot/requirements/{10b8fc3.txt => 1ceca0e.txt} (55%) rename .riot/requirements/{1252d0f.txt => 1e0c0d6.txt} (51%) rename .riot/requirements/{14ca37f.txt => 1e49987.txt} (50%) rename .riot/requirements/{1becf29.txt => 1e67852.txt} (52%) rename .riot/requirements/{1163993.txt => 2a81450.txt} (55%) rename .riot/requirements/{3af9e27.txt => 32540b6.txt} (53%) rename .riot/requirements/{8fd4efc.txt => 3e6ea76.txt} (51%) rename .riot/requirements/{91bec06.txt => 6710b57.txt} (51%) rename .riot/requirements/{1b09cab.txt => 764e316.txt} (53%) rename .riot/requirements/{1f42cb3.txt => 77724eb.txt} (53%) rename .riot/requirements/{11bda89.txt => b908e36.txt} (69%) rename .riot/requirements/{7800b91.txt => c426f16.txt} (65%) rename .riot/requirements/{144615f.txt => cde42be.txt} (50%) delete mode 100644 releasenotes/notes/fix-ci_visibility-use_default_tracer-8523fd1859dea0da.yaml delete mode 100644 tests/snapshots/tests.contrib.pytest.test_pytest_snapshot.test_pytest_with_ddtrace_patch_all.json diff --git a/.riot/requirements/d03449e.txt b/.riot/requirements/117f119.txt similarity index 51% rename from .riot/requirements/d03449e.txt rename to .riot/requirements/117f119.txt index 39414e29954..b71c5320c7d 100644 --- a/.riot/requirements/d03449e.txt +++ b/.riot/requirements/117f119.txt @@ -2,32 +2,24 @@ # This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --config=pyproject.toml --no-annotate .riot/requirements/d03449e.in +# pip-compile --no-annotate .riot/requirements/117f119.in # -anyio==4.3.0 attrs==23.2.0 -certifi==2024.2.2 -coverage[toml]==7.4.4 +coverage[toml]==7.4.1 exceptiongroup==1.2.0 -h11==0.14.0 -httpcore==1.0.5 -httpx==0.27.0 hypothesis==6.45.0 -idna==3.7 -importlib-metadata==7.1.0 +importlib-metadata==7.0.1 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 -msgpack==1.0.8 +msgpack==1.0.7 opentracing==2.4.0 -packaging==24.0 +packaging==23.2 pluggy==1.4.0 -pytest==8.1.1 +pytest==8.0.0 pytest-cov==2.12.0 pytest-mock==2.0.0 pytest-randomly==3.15.0 -sniffio==1.3.1 sortedcontainers==2.4.0 tomli==2.0.1 -typing-extensions==4.11.0 -zipp==3.18.1 +zipp==3.17.0 diff --git a/.riot/requirements/48eb599.txt b/.riot/requirements/1254841.txt similarity index 53% rename from .riot/requirements/48eb599.txt rename to .riot/requirements/1254841.txt index cf67ac7deb3..3639eb9fb05 100644 --- a/.riot/requirements/48eb599.txt +++ b/.riot/requirements/1254841.txt @@ -2,28 +2,21 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-annotate .riot/requirements/48eb599.in +# pip-compile --no-annotate .riot/requirements/1254841.in # -anyio==4.3.0 asynctest==0.13.0 attrs==23.2.0 -certifi==2024.2.2 -coverage[toml]==7.4.4 -h11==0.14.0 -httpcore==1.0.5 -httpx==0.27.0 +coverage[toml]==7.4.1 hypothesis==6.45.0 -idna==3.7 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 -msgpack==1.0.8 +msgpack==1.0.7 opentracing==2.4.0 -packaging==24.0 +packaging==23.2 pluggy==1.4.0 pytest==7.4.4 -pytest-cov==5.0.0 -pytest-mock==3.14.0 +pytest-cov==4.1.0 +pytest-mock==3.12.0 pytest-randomly==3.15.0 -sniffio==1.3.1 sortedcontainers==2.4.0 diff --git a/.riot/requirements/17cf97e.txt b/.riot/requirements/14d5757.txt similarity index 53% rename from .riot/requirements/17cf97e.txt rename to .riot/requirements/14d5757.txt index cec3b324723..36432ffa62c 100644 --- a/.riot/requirements/17cf97e.txt +++ b/.riot/requirements/14d5757.txt @@ -2,28 +2,21 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --no-annotate .riot/requirements/17cf97e.in +# pip-compile --no-annotate .riot/requirements/14d5757.in # -anyio==4.3.0 asynctest==0.13.0 attrs==23.2.0 -certifi==2024.2.2 -coverage[toml]==7.4.4 -h11==0.14.0 -httpcore==1.0.5 -httpx==0.27.0 +coverage[toml]==7.4.1 hypothesis==6.45.0 -idna==3.7 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 -msgpack==1.0.8 +msgpack==1.0.7 opentracing==2.4.0 -packaging==24.0 +packaging==23.2 pluggy==1.4.0 pytest==7.4.4 -pytest-cov==5.0.0 -pytest-mock==3.14.0 +pytest-cov==4.1.0 +pytest-mock==3.12.0 pytest-randomly==3.15.0 -sniffio==1.3.1 sortedcontainers==2.4.0 diff --git a/.riot/requirements/18147c2.txt b/.riot/requirements/17ecd2b.txt similarity index 51% rename from .riot/requirements/18147c2.txt rename to .riot/requirements/17ecd2b.txt index 5c5f8a6a0fd..bb56b30b3e9 100644 --- a/.riot/requirements/18147c2.txt +++ b/.riot/requirements/17ecd2b.txt @@ -2,28 +2,21 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-annotate .riot/requirements/18147c2.in +# pip-compile --no-annotate .riot/requirements/17ecd2b.in # -anyio==4.3.0 asynctest==0.13.0 attrs==23.2.0 -certifi==2024.2.2 -coverage[toml]==7.4.4 -h11==0.14.0 -httpcore==1.0.5 -httpx==0.27.0 +coverage[toml]==7.4.1 hypothesis==6.45.0 -idna==3.7 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 -msgpack==1.0.8 +msgpack==1.0.7 opentracing==2.4.0 -packaging==24.0 +packaging==23.2 pluggy==1.4.0 -pytest==8.1.1 -pytest-cov==5.0.0 -pytest-mock==3.14.0 +pytest==8.0.0 +pytest-cov==4.1.0 +pytest-mock==3.12.0 pytest-randomly==3.15.0 -sniffio==1.3.1 sortedcontainers==2.4.0 diff --git a/.riot/requirements/a0aa271.txt b/.riot/requirements/18cd4dd.txt similarity index 69% rename from .riot/requirements/a0aa271.txt rename to .riot/requirements/18cd4dd.txt index c88c04d4be7..2cf6ca97907 100644 --- a/.riot/requirements/a0aa271.txt +++ b/.riot/requirements/18cd4dd.txt @@ -2,31 +2,24 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --no-annotate --resolver=backtracking .riot/requirements/a0aa271.in +# pip-compile --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/18cd4dd.in # -anyio==3.7.1 attrs==23.2.0 -certifi==2024.2.2 coverage[toml]==7.2.7 exceptiongroup==1.2.0 -h11==0.14.0 -httpcore==0.17.3 -httpx==0.24.1 hypothesis==6.45.0 -idna==3.7 importlib-metadata==6.7.0 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 msgpack==1.0.5 opentracing==2.4.0 -packaging==24.0 +packaging==23.2 pluggy==1.2.0 pytest==7.4.4 pytest-cov==2.12.0 pytest-mock==2.0.0 pytest-randomly==3.12.0 -sniffio==1.3.1 sortedcontainers==2.4.0 tomli==2.0.1 typing-extensions==4.7.1 diff --git a/.riot/requirements/10b8fc3.txt b/.riot/requirements/1ceca0e.txt similarity index 55% rename from .riot/requirements/10b8fc3.txt rename to .riot/requirements/1ceca0e.txt index 9324ae55f3a..52bfc2d3b6c 100644 --- a/.riot/requirements/10b8fc3.txt +++ b/.riot/requirements/1ceca0e.txt @@ -2,30 +2,23 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --no-annotate .riot/requirements/10b8fc3.in +# pip-compile --no-annotate .riot/requirements/1ceca0e.in # -anyio==4.3.0 asynctest==0.13.0 attrs==23.2.0 -certifi==2024.2.2 -coverage[toml]==7.4.4 -h11==0.14.0 -httpcore==1.0.5 -httpx==0.27.0 +coverage[toml]==7.4.1 hypothesis==6.45.0 -idna==3.7 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 -msgpack==1.0.8 +msgpack==1.0.7 opentracing==2.4.0 -packaging==24.0 +packaging==23.2 pluggy==1.4.0 py==1.11.0 pytest==6.2.5 -pytest-cov==5.0.0 -pytest-mock==3.14.0 +pytest-cov==4.1.0 +pytest-mock==3.12.0 pytest-randomly==3.15.0 -sniffio==1.3.1 sortedcontainers==2.4.0 toml==0.10.2 diff --git a/.riot/requirements/1252d0f.txt b/.riot/requirements/1e0c0d6.txt similarity index 51% rename from .riot/requirements/1252d0f.txt rename to .riot/requirements/1e0c0d6.txt index 53b83cf7b70..73febff7227 100644 --- a/.riot/requirements/1252d0f.txt +++ b/.riot/requirements/1e0c0d6.txt @@ -2,28 +2,21 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --no-annotate .riot/requirements/1252d0f.in +# pip-compile --no-annotate .riot/requirements/1e0c0d6.in # -anyio==4.3.0 asynctest==0.13.0 attrs==23.2.0 -certifi==2024.2.2 -coverage[toml]==7.4.4 -h11==0.14.0 -httpcore==1.0.5 -httpx==0.27.0 +coverage[toml]==7.4.1 hypothesis==6.45.0 -idna==3.7 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 -msgpack==1.0.8 +msgpack==1.0.7 opentracing==2.4.0 -packaging==24.0 +packaging==23.2 pluggy==1.4.0 -pytest==8.1.1 -pytest-cov==5.0.0 -pytest-mock==3.14.0 +pytest==8.0.0 +pytest-cov==4.1.0 +pytest-mock==3.12.0 pytest-randomly==3.15.0 -sniffio==1.3.1 sortedcontainers==2.4.0 diff --git a/.riot/requirements/14ca37f.txt b/.riot/requirements/1e49987.txt similarity index 50% rename from .riot/requirements/14ca37f.txt rename to .riot/requirements/1e49987.txt index 0d9ec3e2183..f12227707fa 100644 --- a/.riot/requirements/14ca37f.txt +++ b/.riot/requirements/1e49987.txt @@ -2,33 +2,24 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --no-annotate --resolver=backtracking .riot/requirements/14ca37f.in +# pip-compile --no-annotate .riot/requirements/1e49987.in # -anyio==4.3.0 asynctest==0.13.0 attrs==23.2.0 -certifi==2024.2.2 -coverage[toml]==7.4.4 -exceptiongroup==1.2.0 -h11==0.14.0 -httpcore==1.0.5 -httpx==0.27.0 +coverage[toml]==7.4.1 hypothesis==6.45.0 -idna==3.7 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 -msgpack==1.0.8 +msgpack==1.0.7 opentracing==2.4.0 -packaging==24.0 +packaging==23.2 pluggy==1.4.0 py==1.11.0 pytest==6.2.5 -pytest-cov==5.0.0 -pytest-mock==3.14.0 +pytest-cov==4.1.0 +pytest-mock==3.12.0 pytest-randomly==3.15.0 -sniffio==1.3.1 sortedcontainers==2.4.0 toml==0.10.2 tomli==2.0.1 -typing-extensions==4.11.0 diff --git a/.riot/requirements/1becf29.txt b/.riot/requirements/1e67852.txt similarity index 52% rename from .riot/requirements/1becf29.txt rename to .riot/requirements/1e67852.txt index 9a18af7d058..21bd5692752 100644 --- a/.riot/requirements/1becf29.txt +++ b/.riot/requirements/1e67852.txt @@ -2,31 +2,23 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --no-annotate --resolver=backtracking .riot/requirements/1becf29.in +# pip-compile --no-annotate .riot/requirements/1e67852.in # -anyio==4.3.0 asynctest==0.13.0 attrs==23.2.0 -certifi==2024.2.2 -coverage[toml]==7.4.4 +coverage[toml]==7.4.1 exceptiongroup==1.2.0 -h11==0.14.0 -httpcore==1.0.5 -httpx==0.27.0 hypothesis==6.45.0 -idna==3.7 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 -msgpack==1.0.8 +msgpack==1.0.7 opentracing==2.4.0 -packaging==24.0 +packaging==23.2 pluggy==1.4.0 pytest==7.4.4 -pytest-cov==5.0.0 -pytest-mock==3.14.0 +pytest-cov==4.1.0 +pytest-mock==3.12.0 pytest-randomly==3.15.0 -sniffio==1.3.1 sortedcontainers==2.4.0 tomli==2.0.1 -typing-extensions==4.11.0 diff --git a/.riot/requirements/1163993.txt b/.riot/requirements/2a81450.txt similarity index 55% rename from .riot/requirements/1163993.txt rename to .riot/requirements/2a81450.txt index 6ecfee3c50b..c9dbb88511a 100644 --- a/.riot/requirements/1163993.txt +++ b/.riot/requirements/2a81450.txt @@ -2,30 +2,23 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-annotate .riot/requirements/1163993.in +# pip-compile --no-annotate .riot/requirements/2a81450.in # -anyio==4.3.0 asynctest==0.13.0 attrs==23.2.0 -certifi==2024.2.2 -coverage[toml]==7.4.4 -h11==0.14.0 -httpcore==1.0.5 -httpx==0.27.0 +coverage[toml]==7.4.1 hypothesis==6.45.0 -idna==3.7 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 -msgpack==1.0.8 +msgpack==1.0.7 opentracing==2.4.0 -packaging==24.0 +packaging==23.2 pluggy==1.4.0 py==1.11.0 pytest==6.2.5 -pytest-cov==5.0.0 -pytest-mock==3.14.0 +pytest-cov==4.1.0 +pytest-mock==3.12.0 pytest-randomly==3.15.0 -sniffio==1.3.1 sortedcontainers==2.4.0 toml==0.10.2 diff --git a/.riot/requirements/3af9e27.txt b/.riot/requirements/32540b6.txt similarity index 53% rename from .riot/requirements/3af9e27.txt rename to .riot/requirements/32540b6.txt index 09231897bd8..346a6890b0f 100644 --- a/.riot/requirements/3af9e27.txt +++ b/.riot/requirements/32540b6.txt @@ -2,32 +2,24 @@ # This file is autogenerated by pip-compile with Python 3.8 # by the following command: # -# pip-compile --no-annotate .riot/requirements/3af9e27.in +# pip-compile --no-annotate .riot/requirements/32540b6.in # -anyio==4.3.0 attrs==23.2.0 -certifi==2024.2.2 -coverage[toml]==7.4.4 +coverage[toml]==7.4.1 exceptiongroup==1.2.0 -h11==0.14.0 -httpcore==1.0.5 -httpx==0.27.0 hypothesis==6.45.0 -idna==3.7 -importlib-metadata==7.1.0 +importlib-metadata==7.0.1 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 -msgpack==1.0.8 +msgpack==1.0.7 opentracing==2.4.0 -packaging==24.0 +packaging==23.2 pluggy==1.4.0 -pytest==8.1.1 +pytest==8.0.0 pytest-cov==2.12.0 pytest-mock==2.0.0 pytest-randomly==3.15.0 -sniffio==1.3.1 sortedcontainers==2.4.0 tomli==2.0.1 -typing-extensions==4.11.0 -zipp==3.18.1 +zipp==3.17.0 diff --git a/.riot/requirements/8fd4efc.txt b/.riot/requirements/3e6ea76.txt similarity index 51% rename from .riot/requirements/8fd4efc.txt rename to .riot/requirements/3e6ea76.txt index a7217c530cd..9f2f450e61f 100644 --- a/.riot/requirements/8fd4efc.txt +++ b/.riot/requirements/3e6ea76.txt @@ -2,33 +2,24 @@ # This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --config=pyproject.toml --no-annotate .riot/requirements/8fd4efc.in +# pip-compile --no-annotate .riot/requirements/3e6ea76.in # -anyio==4.3.0 attrs==23.2.0 -certifi==2024.2.2 -coverage==7.4.4 -exceptiongroup==1.2.0 -h11==0.14.0 -httpcore==1.0.5 -httpx==0.27.0 +coverage==7.4.1 hypothesis==6.45.0 -idna==3.7 -importlib-metadata==7.1.0 +importlib-metadata==7.0.1 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 -msgpack==1.0.8 +msgpack==1.0.7 opentracing==2.4.0 -packaging==24.0 +packaging==23.2 pluggy==1.4.0 py==1.11.0 pytest==6.2.5 pytest-cov==2.9.0 pytest-mock==2.0.0 pytest-randomly==3.15.0 -sniffio==1.3.1 sortedcontainers==2.4.0 toml==0.10.2 -typing-extensions==4.11.0 -zipp==3.18.1 +zipp==3.17.0 diff --git a/.riot/requirements/91bec06.txt b/.riot/requirements/6710b57.txt similarity index 51% rename from .riot/requirements/91bec06.txt rename to .riot/requirements/6710b57.txt index 15f0875ec11..abc0c7a3a4d 100644 --- a/.riot/requirements/91bec06.txt +++ b/.riot/requirements/6710b57.txt @@ -2,33 +2,24 @@ # This file is autogenerated by pip-compile with Python 3.8 # by the following command: # -# pip-compile --config=pyproject.toml --no-annotate .riot/requirements/91bec06.in +# pip-compile --no-annotate .riot/requirements/6710b57.in # -anyio==4.3.0 attrs==23.2.0 -certifi==2024.2.2 -coverage==7.4.4 -exceptiongroup==1.2.0 -h11==0.14.0 -httpcore==1.0.5 -httpx==0.27.0 +coverage==7.4.1 hypothesis==6.45.0 -idna==3.7 -importlib-metadata==7.1.0 +importlib-metadata==7.0.1 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 -msgpack==1.0.8 +msgpack==1.0.7 opentracing==2.4.0 -packaging==24.0 +packaging==23.2 pluggy==1.4.0 py==1.11.0 pytest==6.2.5 pytest-cov==2.9.0 pytest-mock==2.0.0 pytest-randomly==3.15.0 -sniffio==1.3.1 sortedcontainers==2.4.0 toml==0.10.2 -typing-extensions==4.11.0 -zipp==3.18.1 +zipp==3.17.0 diff --git a/.riot/requirements/1b09cab.txt b/.riot/requirements/764e316.txt similarity index 53% rename from .riot/requirements/1b09cab.txt rename to .riot/requirements/764e316.txt index a8fd716e3c9..ebece799c14 100644 --- a/.riot/requirements/1b09cab.txt +++ b/.riot/requirements/764e316.txt @@ -2,32 +2,24 @@ # This file is autogenerated by pip-compile with Python 3.8 # by the following command: # -# pip-compile --config=pyproject.toml --no-annotate .riot/requirements/1b09cab.in +# pip-compile --no-annotate .riot/requirements/764e316.in # -anyio==4.3.0 attrs==23.2.0 -certifi==2024.2.2 -coverage[toml]==7.4.4 +coverage[toml]==7.4.1 exceptiongroup==1.2.0 -h11==0.14.0 -httpcore==1.0.5 -httpx==0.27.0 hypothesis==6.45.0 -idna==3.7 -importlib-metadata==7.1.0 +importlib-metadata==7.0.1 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 -msgpack==1.0.8 +msgpack==1.0.7 opentracing==2.4.0 -packaging==24.0 +packaging==23.2 pluggy==1.4.0 pytest==7.4.4 pytest-cov==2.12.0 pytest-mock==2.0.0 pytest-randomly==3.15.0 -sniffio==1.3.1 sortedcontainers==2.4.0 tomli==2.0.1 -typing-extensions==4.11.0 -zipp==3.18.1 +zipp==3.17.0 diff --git a/.riot/requirements/1f42cb3.txt b/.riot/requirements/77724eb.txt similarity index 53% rename from .riot/requirements/1f42cb3.txt rename to .riot/requirements/77724eb.txt index 2d3467ff27a..327cbb26b12 100644 --- a/.riot/requirements/1f42cb3.txt +++ b/.riot/requirements/77724eb.txt @@ -2,32 +2,24 @@ # This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --config=pyproject.toml --no-annotate .riot/requirements/1f42cb3.in +# pip-compile --no-annotate .riot/requirements/77724eb.in # -anyio==4.3.0 attrs==23.2.0 -certifi==2024.2.2 -coverage[toml]==7.4.4 +coverage[toml]==7.4.1 exceptiongroup==1.2.0 -h11==0.14.0 -httpcore==1.0.5 -httpx==0.27.0 hypothesis==6.45.0 -idna==3.7 -importlib-metadata==7.1.0 +importlib-metadata==7.0.1 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 -msgpack==1.0.8 +msgpack==1.0.7 opentracing==2.4.0 -packaging==24.0 +packaging==23.2 pluggy==1.4.0 pytest==7.4.4 pytest-cov==2.12.0 pytest-mock==2.0.0 pytest-randomly==3.15.0 -sniffio==1.3.1 sortedcontainers==2.4.0 tomli==2.0.1 -typing-extensions==4.11.0 -zipp==3.18.1 +zipp==3.17.0 diff --git a/.riot/requirements/11bda89.txt b/.riot/requirements/b908e36.txt similarity index 69% rename from .riot/requirements/11bda89.txt rename to .riot/requirements/b908e36.txt index d33a99885ff..d210b2430f5 100644 --- a/.riot/requirements/11bda89.txt +++ b/.riot/requirements/b908e36.txt @@ -2,31 +2,24 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --no-annotate --resolver=backtracking .riot/requirements/11bda89.in +# pip-compile --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/b908e36.in # -anyio==3.7.1 attrs==23.2.0 -certifi==2024.2.2 coverage[toml]==7.2.7 exceptiongroup==1.2.0 -h11==0.14.0 -httpcore==0.17.3 -httpx==0.24.1 hypothesis==6.45.0 -idna==3.7 importlib-metadata==6.7.0 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 msgpack==1.0.5 opentracing==2.4.0 -packaging==24.0 +packaging==23.2 pluggy==1.2.0 pytest==7.4.4 pytest-cov==2.12.0 pytest-mock==2.0.0 pytest-randomly==3.12.0 -sniffio==1.3.1 sortedcontainers==2.4.0 tomli==2.0.1 typing-extensions==4.7.1 diff --git a/.riot/requirements/7800b91.txt b/.riot/requirements/c426f16.txt similarity index 65% rename from .riot/requirements/7800b91.txt rename to .riot/requirements/c426f16.txt index b64794723bc..4ee102b0707 100644 --- a/.riot/requirements/7800b91.txt +++ b/.riot/requirements/c426f16.txt @@ -2,32 +2,24 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --no-annotate --resolver=backtracking .riot/requirements/7800b91.in +# pip-compile --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/c426f16.in # -anyio==3.7.1 attrs==23.2.0 -certifi==2024.2.2 coverage==7.2.7 -exceptiongroup==1.2.0 -h11==0.14.0 -httpcore==0.17.3 -httpx==0.24.1 hypothesis==6.45.0 -idna==3.7 importlib-metadata==6.7.0 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 msgpack==1.0.5 opentracing==2.4.0 -packaging==24.0 +packaging==23.2 pluggy==1.2.0 py==1.11.0 pytest==6.2.5 pytest-cov==2.9.0 pytest-mock==2.0.0 pytest-randomly==3.12.0 -sniffio==1.3.1 sortedcontainers==2.4.0 toml==0.10.2 typing-extensions==4.7.1 diff --git a/.riot/requirements/144615f.txt b/.riot/requirements/cde42be.txt similarity index 50% rename from .riot/requirements/144615f.txt rename to .riot/requirements/cde42be.txt index ab3ffaf7ef7..3ec46eba267 100644 --- a/.riot/requirements/144615f.txt +++ b/.riot/requirements/cde42be.txt @@ -2,31 +2,23 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --no-annotate --resolver=backtracking .riot/requirements/144615f.in +# pip-compile --no-annotate .riot/requirements/cde42be.in # -anyio==4.3.0 asynctest==0.13.0 attrs==23.2.0 -certifi==2024.2.2 -coverage[toml]==7.4.4 +coverage[toml]==7.4.1 exceptiongroup==1.2.0 -h11==0.14.0 -httpcore==1.0.5 -httpx==0.27.0 hypothesis==6.45.0 -idna==3.7 iniconfig==2.0.0 mock==5.1.0 more-itertools==8.10.0 -msgpack==1.0.8 +msgpack==1.0.7 opentracing==2.4.0 -packaging==24.0 +packaging==23.2 pluggy==1.4.0 -pytest==8.1.1 -pytest-cov==5.0.0 -pytest-mock==3.14.0 +pytest==8.0.0 +pytest-cov==4.1.0 +pytest-mock==3.12.0 pytest-randomly==3.15.0 -sniffio==1.3.1 sortedcontainers==2.4.0 tomli==2.0.1 -typing-extensions==4.11.0 diff --git a/ddtrace/internal/ci_visibility/filters.py b/ddtrace/internal/ci_visibility/filters.py index c90e7324533..a6e4db86bd6 100644 --- a/ddtrace/internal/ci_visibility/filters.py +++ b/ddtrace/internal/ci_visibility/filters.py @@ -34,6 +34,7 @@ def process_trace(self, trace): local_root.context.sampling_priority = AUTO_KEEP for span in trace: span.set_tags(self._tags) + span.service = self._service span.set_tag_str(ci.LIBRARY_VERSION, ddtrace.__version__) return trace diff --git a/ddtrace/internal/ci_visibility/recorder.py b/ddtrace/internal/ci_visibility/recorder.py index 93efc2fc913..71c2e1df414 100644 --- a/ddtrace/internal/ci_visibility/recorder.py +++ b/ddtrace/internal/ci_visibility/recorder.py @@ -10,7 +10,6 @@ from typing import Union # noqa:F401 from uuid import uuid4 -import ddtrace from ddtrace import Tracer from ddtrace import config as ddconfig from ddtrace.contrib import trace_utils @@ -162,7 +161,7 @@ def __init__(self, tracer=None, config=None, service=None): # Create a new CI tracer self.tracer = Tracer(context_provider=CIContextProvider()) else: - self.tracer = ddtrace.tracer + self.tracer = Tracer() # Partial traces are required for ITR to work in suite-level skipping for long test sessions, but we # assume that a tracer is already configured if it's been passed in. diff --git a/releasenotes/notes/fix-ci_visibility-use_default_tracer-8523fd1859dea0da.yaml b/releasenotes/notes/fix-ci_visibility-use_default_tracer-8523fd1859dea0da.yaml deleted file mode 100644 index a8c8eceda78..00000000000 --- a/releasenotes/notes/fix-ci_visibility-use_default_tracer-8523fd1859dea0da.yaml +++ /dev/null @@ -1,4 +0,0 @@ ---- -fixes: - - | - CI Visibility: fixes that traces were not properly being sent in agentless mode, and were otherwise not properly attached to the test that started them diff --git a/riotfile.py b/riotfile.py index 7b891582aab..7ec9baab4e8 100644 --- a/riotfile.py +++ b/riotfile.py @@ -1580,7 +1580,6 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): "msgpack": latest, "more_itertools": "<8.11.0", "pytest-mock": "==2.0.0", - "httpx": latest, }, venvs=[ Venv( @@ -1608,7 +1607,6 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): "msgpack": latest, "asynctest": "==0.13.0", "more_itertools": "<8.11.0", - "httpx": latest, }, ), ], diff --git a/tests/ci_visibility/test_ci_visibility.py b/tests/ci_visibility/test_ci_visibility.py index 221a8d7be46..56e89b4bd85 100644 --- a/tests/ci_visibility/test_ci_visibility.py +++ b/tests/ci_visibility/test_ci_visibility.py @@ -66,6 +66,7 @@ def test_filters_test_spans(): # Root span in trace is a test trace = [root_test_span] assert trace_filter.process_trace(trace) == trace + assert root_test_span.service == "test-service" assert root_test_span.get_tag(ci.LIBRARY_VERSION) == ddtrace.__version__ assert root_test_span.get_tag("hello") == "world" assert root_test_span.context.dd_origin == ci.CI_APP_TEST_ORIGIN diff --git a/tests/contrib/pytest/test_pytest_snapshot.py b/tests/contrib/pytest/test_pytest_snapshot.py index d73ab68377e..80d4bbadb21 100644 --- a/tests/contrib/pytest/test_pytest_snapshot.py +++ b/tests/contrib/pytest/test_pytest_snapshot.py @@ -28,7 +28,6 @@ "duration", "start", ] -SNAPSHOT_IGNORES_PATCH_ALL = SNAPSHOT_IGNORES + ["meta.http.useragent"] SNAPSHOT_IGNORES_ITR_COVERAGE = ["metrics.test.source.start", "metrics.test.source.end", "meta.test.source.file"] @@ -119,28 +118,3 @@ def test_add_two_number_list(): return_value=_CIVisibilitySettings(False, False, False, False), ): subprocess.run(["ddtrace-run", "coverage", "run", "--include=nothing.py", "-m", "pytest", "--ddtrace"]) - - @snapshot(ignores=SNAPSHOT_IGNORES_PATCH_ALL) - def test_pytest_with_ddtrace_patch_all(self): - call_httpx = """ - import httpx - - def call_httpx(): - return httpx.get("http://localhost:9126/bad_path.cgi") - """ - self.testdir.makepyfile(call_httpx=call_httpx) - test_call_httpx = """ - from call_httpx import call_httpx - - def test_call_urllib(): - r = call_httpx() - assert r.status_code == 404 - """ - self.testdir.makepyfile(test_call_httpx=test_call_httpx) - self.testdir.chdir() - with override_env(dict(DD_API_KEY="foobar.baz", DD_CIVISIBILITY_ITR_ENABLED="false")): - with mock.patch( - "ddtrace.internal.ci_visibility.recorder.CIVisibility._check_settings_api", - return_value=_CIVisibilitySettings(False, False, False, False), - ): - subprocess.run(["pytest", "--ddtrace", "--ddtrace-patch-all"]) diff --git a/tests/snapshots/tests.contrib.pytest.test_pytest_snapshot.test_pytest_with_ddtrace_patch_all.json b/tests/snapshots/tests.contrib.pytest.test_pytest_snapshot.test_pytest_with_ddtrace_patch_all.json deleted file mode 100644 index 6c842085cc1..00000000000 --- a/tests/snapshots/tests.contrib.pytest.test_pytest_snapshot.test_pytest_with_ddtrace_patch_all.json +++ /dev/null @@ -1,215 +0,0 @@ -[[ - { - "name": "pytest.test_session", - "service": "pytest", - "resource": "pytest.test_session", - "trace_id": 0, - "span_id": 1, - "parent_id": 0, - "type": "test", - "error": 0, - "meta": { - "_dd.base_service": "", - "_dd.origin": "ciapp-test", - "_dd.p.dm": "-0", - "_dd.p.tid": "661fce1b00000000", - "component": "pytest", - "language": "python", - "library_version": "2.9.0.dev80+gae109804d.d20240417", - "os.architecture": "aarch64", - "os.platform": "Linux", - "os.version": "5.15.49-linuxkit-pr", - "runtime-id": "0a9eec171fca451babccd0136aa32c67", - "runtime.name": "CPython", - "runtime.version": "3.8.16", - "span.kind": "test", - "test.code_coverage.enabled": "false", - "test.command": "pytest --ddtrace --ddtrace-patch-all", - "test.framework": "pytest", - "test.framework_version": "8.1.1", - "test.itr.tests_skipping.enabled": "false", - "test.status": "pass", - "test_session_id": "10252982646231086668", - "type": "test_session_end" - }, - "metrics": { - "_dd.py.partial_flush": 1, - "_dd.top_level": 1, - "_dd.tracer_kr": 1.0, - "_sampling_priority_v1": 1, - "process_id": 44747 - }, - "duration": 175777694, - "start": 1713360411000016472 - }, - { - "name": "pytest.test_module", - "service": "pytest", - "resource": "pytest.test_module", - "trace_id": 0, - "span_id": 2, - "parent_id": 1, - "type": "test", - "error": 0, - "meta": { - "_dd.base_service": "", - "_dd.origin": "ciapp-test", - "_dd.p.dm": "-0", - "_dd.p.tid": "661fce1b00000000", - "component": "pytest", - "language": "python", - "library_version": "2.9.0.dev80+gae109804d.d20240417", - "os.architecture": "aarch64", - "os.platform": "Linux", - "os.version": "5.15.49-linuxkit-pr", - "runtime.name": "CPython", - "runtime.version": "3.8.16", - "span.kind": "test", - "test.code_coverage.enabled": "false", - "test.command": "pytest --ddtrace --ddtrace-patch-all", - "test.framework": "pytest", - "test.framework_version": "8.1.1", - "test.itr.tests_skipping.enabled": "false", - "test.module": "", - "test.module_path": "", - "test.status": "pass", - "test_module_id": "5968154422818595882", - "test_session_id": "10252982646231086668", - "type": "test_module_end" - }, - "metrics": { - "_dd.py.partial_flush": 1, - "_dd.tracer_kr": 1.0, - "_sampling_priority_v1": 1 - }, - "duration": 54086708, - "start": 1713360411121294000 - }, - { - "name": "pytest.test_suite", - "service": "pytest", - "resource": "pytest.test_suite", - "trace_id": 0, - "span_id": 3, - "parent_id": 2, - "type": "test", - "error": 0, - "meta": { - "_dd.base_service": "", - "_dd.origin": "ciapp-test", - "_dd.p.dm": "-0", - "_dd.p.tid": "661fce1b00000000", - "component": "pytest", - "language": "python", - "library_version": "2.9.0.dev80+gae109804d.d20240417", - "os.architecture": "aarch64", - "os.platform": "Linux", - "os.version": "5.15.49-linuxkit-pr", - "runtime.name": "CPython", - "runtime.version": "3.8.16", - "span.kind": "test", - "test.command": "pytest --ddtrace --ddtrace-patch-all", - "test.framework": "pytest", - "test.framework_version": "8.1.1", - "test.module": "", - "test.module_path": "", - "test.status": "pass", - "test.suite": "test_call_httpx.py", - "test_module_id": "5968154422818595882", - "test_session_id": "10252982646231086668", - "test_suite_id": "937891247795205250", - "type": "test_suite_end" - }, - "metrics": { - "_dd.py.partial_flush": 1, - "_dd.tracer_kr": 1.0, - "_sampling_priority_v1": 1 - }, - "duration": 53712375, - "start": 1713360411121370750 - }], -[ - { - "name": "pytest.test", - "service": "pytest", - "resource": "test_call_httpx.py::test_call_urllib", - "trace_id": 1, - "span_id": 1, - "parent_id": 0, - "type": "test", - "error": 0, - "meta": { - "_dd.base_service": "", - "_dd.origin": "ciapp-test", - "_dd.p.dm": "-0", - "_dd.p.tid": "661fce1b00000000", - "component": "pytest", - "language": "python", - "library_version": "2.9.0.dev80+gae109804d.d20240417", - "os.architecture": "aarch64", - "os.platform": "Linux", - "os.version": "5.15.49-linuxkit-pr", - "runtime-id": "0a9eec171fca451babccd0136aa32c67", - "runtime.name": "CPython", - "runtime.version": "3.8.16", - "span.kind": "test", - "test.command": "pytest --ddtrace --ddtrace-patch-all", - "test.framework": "pytest", - "test.framework_version": "8.1.1", - "test.module": "", - "test.module_path": "", - "test.name": "test_call_urllib", - "test.source.file": "test_call_httpx.py", - "test.status": "pass", - "test.suite": "test_call_httpx.py", - "test.type": "test", - "test_module_id": "5968154422818595882", - "test_session_id": "10252982646231086668", - "test_suite_id": "937891247795205250", - "type": "test" - }, - "metrics": { - "_dd.py.partial_flush": 1, - "_dd.top_level": 1, - "_dd.tracer_kr": 1.0, - "_sampling_priority_v1": 1, - "process_id": 44747, - "test.source.end": 6, - "test.source.start": 3 - }, - "duration": 54019542, - "start": 1713360411121402458 - }], -[ - { - "name": "http.request", - "service": "", - "resource": "http.request", - "trace_id": 2, - "span_id": 1, - "parent_id": 0, - "type": "http", - "error": 0, - "meta": { - "_dd.p.dm": "-0", - "_dd.p.tid": "661fce1b00000000", - "component": "httpx", - "http.method": "GET", - "http.status_code": "404", - "http.url": "http://localhost:9126/bad_path.cgi", - "http.useragent": "this_should_never_match", - "language": "python", - "out.host": "localhost", - "runtime-id": "0a9eec171fca451babccd0136aa32c67", - "span.kind": "client" - }, - "metrics": { - "_dd.measured": 1, - "_dd.top_level": 1, - "_dd.tracer_kr": 1.0, - "_sampling_priority_v1": 1, - "process_id": 44747 - }, - "duration": 2077333, - "start": 1713360411171812125 - }]] From f53484d95e528156b8a839f6ab53aac8594cbc79 Mon Sep 17 00:00:00 2001 From: Juanjo Alvarez Martinez Date: Wed, 22 May 2024 11:01:08 +0200 Subject: [PATCH 092/104] chore(asm): avoid terminate being called on native aspect (#9330) ## Description This makes the bytearray extend native aspect and the join aspect raise an exception instead of calling `terminate()` if there is an error when calling `PyObject_CallMethodObjArgs`. I checked all the aspects manually and only these ones called `terminate()` when there was an error, all the other ones errors were correctly intercepted by `pybind11` and converted to Python exceptions, so it must be something related to this specific Python C API call. This also add Python front-ends for all the `os.path` aspects. While these would not call `terminate()` on exception, they would raise a `pybind11` exception which could confuse the users. The front-end aspects will call the original one on any exception, just like the other front-ends do, so if there is a real error the user will receive the right error. ## Checklist - [X] Change(s) are motivated and described in the PR description - [X] Testing strategy is described if automated tests are not included in the PR - [X] Risks are described (performance impact, potential for breakage, maintainability) - [X] Change is maintainable (easy to change, telemetry, documentation) - [X] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [X] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [X] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [X] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [ ] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Signed-off-by: Juanjo Alvarez Co-authored-by: Alberto Vara --- ddtrace/appsec/_iast/_ast/visitor.py | 16 +- .../_taint_tracking/Aspects/AspectExtend.cpp | 12 ++ .../_taint_tracking/Aspects/AspectJoin.cpp | 9 ++ .../_iast/_taint_tracking/Aspects/Helpers.cpp | 19 +++ .../_iast/_taint_tracking/Aspects/Helpers.h | 3 + .../appsec/_iast/_taint_tracking/aspects.py | 80 ++++++++++ ...-extend-join-aspects-0cf5ddcaaf836168.yaml | 4 + .../aspects/test_bytearray_extend_aspect.py | 8 + .../iast/aspects/test_ospath_aspects.py | 138 +++++++++--------- 9 files changed, 212 insertions(+), 77 deletions(-) create mode 100644 releasenotes/notes/no-terminate-on-extend-join-aspects-0cf5ddcaaf836168.yaml diff --git a/ddtrace/appsec/_iast/_ast/visitor.py b/ddtrace/appsec/_iast/_ast/visitor.py index 534e721abdf..4394ff0b6d2 100644 --- a/ddtrace/appsec/_iast/_ast/visitor.py +++ b/ddtrace/appsec/_iast/_ast/visitor.py @@ -74,12 +74,12 @@ def _mark_avoid_convert_recursively(node): # Replacement functions for modules "module_functions": { "os.path": { - "basename": "ddtrace_aspects._aspect_ospathbasename", - "dirname": "ddtrace_aspects._aspect_ospathdirname", - "join": "ddtrace_aspects._aspect_ospathjoin", - "normcase": "ddtrace_aspects._aspect_ospathnormcase", - "split": "ddtrace_aspects._aspect_ospathsplit", - "splitext": "ddtrace_aspects._aspect_ospathsplitext", + "basename": "ddtrace_aspects.ospathbasename_aspect", + "dirname": "ddtrace_aspects.ospathdirname_aspect", + "join": "ddtrace_aspects.ospathjoin_aspect", + "normcase": "ddtrace_aspects.ospathnormcase_aspect", + "split": "ddtrace_aspects.ospathsplit_aspect", + "splitext": "ddtrace_aspects.ospathsplitext_aspect", } }, "operators": { @@ -132,10 +132,10 @@ def _mark_avoid_convert_recursively(node): if sys.version_info >= (3, 12): - _ASPECTS_SPEC["module_functions"]["os.path"]["splitroot"] = "ddtrace_aspects._aspect_ospathsplitroot" + _ASPECTS_SPEC["module_functions"]["os.path"]["splitroot"] = "ddtrace_aspects.ospathsplitroot_aspect" if sys.version_info >= (3, 12) or os.name == "nt": - _ASPECTS_SPEC["module_functions"]["os.path"]["splitdrive"] = "ddtrace_aspects._aspect_ospathsplitdrive" + _ASPECTS_SPEC["module_functions"]["os.path"]["splitdrive"] = "ddtrace_aspects.ospathsplitdrive_aspect" class AstVisitor(ast.NodeTransformer): diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectExtend.cpp b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectExtend.cpp index e571713342d..78edc217f3c 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectExtend.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectExtend.cpp @@ -1,5 +1,7 @@ #include "AspectExtend.h" +#include "Helpers.h" + /** * @brief Taint candidate_text when bytearray extends is called. * @@ -17,12 +19,14 @@ api_extend_aspect(PyObject* self, PyObject* const* args, const Py_ssize_t nargs) PyObject* candidate_text = args[0]; if (!PyByteArray_Check(candidate_text)) { + py::set_error(PyExc_TypeError, "The candidate text must be a bytearray."); return nullptr; } auto len_candidate_text = PyByteArray_Size(candidate_text); PyObject* to_add = args[1]; if (!PyByteArray_Check(to_add) and !PyBytes_Check(to_add)) { + py::set_error(PyExc_TypeError, "The text to add must be a bytearray or bytes."); return nullptr; } @@ -30,6 +34,10 @@ api_extend_aspect(PyObject* self, PyObject* const* args, const Py_ssize_t nargs) if (not ctx_map or ctx_map->empty()) { auto method_name = PyUnicode_FromString("extend"); PyObject_CallMethodObjArgs(candidate_text, method_name, to_add, nullptr); + if (has_pyerr()) { + Py_DecRef(method_name); + return nullptr; + } Py_DecRef(method_name); } else { const auto& to_candidate = get_tainted_object(candidate_text, ctx_map); @@ -39,6 +47,10 @@ api_extend_aspect(PyObject* self, PyObject* const* args, const Py_ssize_t nargs) // Ensure no returns are done before this method call auto method_name = PyUnicode_FromString("extend"); PyObject_CallMethodObjArgs(candidate_text, method_name, to_add, nullptr); + if (has_pyerr()) { + Py_DecRef(method_name); + return nullptr; + } Py_DecRef(method_name); if (to_result == nullptr) { diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectJoin.cpp b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectJoin.cpp index e190247cebb..5fd205c8a19 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectJoin.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectJoin.cpp @@ -1,5 +1,7 @@ #include "AspectJoin.h" +#include "Helpers.h" + PyObject* aspect_join_str(PyObject* sep, PyObject* result, @@ -177,6 +179,13 @@ api_join_aspect(PyObject* self, PyObject* const* args, const Py_ssize_t nargs) Py_INCREF(result); } + if (has_pyerr()) { + if (decref_arg0) { + Py_DecRef(arg0); + } + return nullptr; + } + const auto ctx_map = initializer->get_tainting_map(); if (not ctx_map or ctx_map->empty() or get_pyobject_size(result) == 0) { // Empty result cannot have taint ranges diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/Helpers.cpp b/ddtrace/appsec/_iast/_taint_tracking/Aspects/Helpers.cpp index 926d22fef6f..3a048fe0a6e 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/Helpers.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/Helpers.cpp @@ -420,6 +420,24 @@ parse_params(size_t position, return default_value; } +bool +has_pyerr() +{ + if (const auto exception = PyErr_Occurred()) { + PyObject *extype, *value, *traceback; + PyErr_Fetch(&extype, &value, &traceback); + PyErr_NormalizeException(&extype, &value, &traceback); + const auto exception_msg = py::str(PyObject_Str(value)); + py::set_error(extype, exception_msg); + Py_DecRef(extype); + Py_DecRef(value); + Py_DecRef(traceback); + return true; + } + + return false; +} + void pyexport_aspect_helpers(py::module& m) { @@ -492,4 +510,5 @@ pyexport_aspect_helpers(py::module& m) "taint_escaped_text"_a, "ranges_orig"_a); m.def("parse_params", &parse_params); + m.def("has_pyerr", &has_pyerr); } diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/Helpers.h b/ddtrace/appsec/_iast/_taint_tracking/Aspects/Helpers.h index 7a9504e21a3..8a5bbe4cf22 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/Helpers.h +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/Helpers.h @@ -66,5 +66,8 @@ api_set_ranges_on_splitted(const StrType& source_str, const py::list& split_result, bool include_separator = false); +bool +has_pyerr(); + void pyexport_aspect_helpers(py::module& m); diff --git a/ddtrace/appsec/_iast/_taint_tracking/aspects.py b/ddtrace/appsec/_iast/_taint_tracking/aspects.py index f4d2d6d63d7..49e50dca773 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/aspects.py +++ b/ddtrace/appsec/_iast/_taint_tracking/aspects.py @@ -540,6 +540,86 @@ def format_value_aspect( return new_text +def ospathjoin_aspect(*args, **kwargs): + try: + return _aspect_ospathjoin(*args, **kwargs) + except Exception as e: + iast_taint_log_error("IAST propagation error. ospathjoin_aspect. {}".format(e)) + import os.path + + return os.path.join(*args, **kwargs) + + +def ospathbasename_aspect(*args, **kwargs): + try: + return _aspect_ospathbasename(*args, **kwargs) + except Exception as e: + iast_taint_log_error("IAST propagation error. ospathbasename_aspect. {}".format(e)) + import os.path + + return os.path.basename(*args, **kwargs) + + +def ospathdirname_aspect(*args, **kwargs): + try: + return _aspect_ospathdirname(*args, **kwargs) + except Exception as e: + iast_taint_log_error("IAST propagation error. ospathdirname_aspect. {}".format(e)) + import os.path + + return os.path.dirname(*args, **kwargs) + + +def ospathsplit_aspect(*args, **kwargs): + try: + return _aspect_ospathsplit(*args, **kwargs) + except Exception as e: + iast_taint_log_error("IAST propagation error. ospathsplit_aspect. {}".format(e)) + import os.path + + return os.path.split(*args, **kwargs) + + +def ospathsplitext_aspect(*args, **kwargs): + try: + return _aspect_ospathsplitext(*args, **kwargs) + except Exception as e: + iast_taint_log_error("IAST propagation error. ospathsplitext_aspect. {}".format(e)) + import os.path + + return os.path.splitext(*args, **kwargs) + + +def ospathsplitroot_aspect(*args, **kwargs): + try: + return _aspect_ospathsplitroot(*args, **kwargs) + except Exception as e: + iast_taint_log_error("IAST propagation error. ospathsplitroot_aspect. {}".format(e)) + import os.path + + return os.path.splitroot(*args, **kwargs) + + +def ospathnormcase_aspect(*args, **kwargs): + try: + return _aspect_ospathnormcase(*args, **kwargs) + except Exception as e: + iast_taint_log_error("IAST propagation error. ospathnormcase_aspect. {}".format(e)) + import os.path + + return os.path.normcase(*args, **kwargs) + + +def ospathsplitdrive_aspect(*args, **kwargs): + try: + return _aspect_ospathsplitdrive(*args, **kwargs) + except Exception as e: + iast_taint_log_error("IAST propagation error. ospathsplitdrive_aspect. {}".format(e)) + import os.path + + return os.path.splitdrive(*args, **kwargs) + + def incremental_translation(self, incr_coder, funcode, empty): tainted_ranges = iter(get_tainted_ranges(self)) result_list, new_ranges = [], [] diff --git a/releasenotes/notes/no-terminate-on-extend-join-aspects-0cf5ddcaaf836168.yaml b/releasenotes/notes/no-terminate-on-extend-join-aspects-0cf5ddcaaf836168.yaml new file mode 100644 index 00000000000..d4659ab2675 --- /dev/null +++ b/releasenotes/notes/no-terminate-on-extend-join-aspects-0cf5ddcaaf836168.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + Code Security: avoid calling terminate on the extend and join aspect when an exception is raised. diff --git a/tests/appsec/iast/aspects/test_bytearray_extend_aspect.py b/tests/appsec/iast/aspects/test_bytearray_extend_aspect.py index 7c4fc89ec9a..d540ae129cf 100644 --- a/tests/appsec/iast/aspects/test_bytearray_extend_aspect.py +++ b/tests/appsec/iast/aspects/test_bytearray_extend_aspect.py @@ -35,6 +35,14 @@ def test_extend_with_bytes_not_tainted(self): assert result == bytearray(b"123456") assert not get_tainted_ranges(result) + def test_extend_native_exception_no_crash(self): + from ddtrace.appsec._iast._taint_tracking.aspects import _extend_aspect + + ba1 = bytearray(b"123") + b2 = 456 + with pytest.raises(TypeError): + _extend_aspect(ba1, b2) + def test_extend_first_tainted(self): ba1 = taint_pyobject( pyobject=bytearray(b"123"), source_name="test", source_value="foo", source_origin=OriginType.PARAMETER diff --git a/tests/appsec/iast/aspects/test_ospath_aspects.py b/tests/appsec/iast/aspects/test_ospath_aspects.py index 87d60ad6a4d..976327cdd2c 100644 --- a/tests/appsec/iast/aspects/test_ospath_aspects.py +++ b/tests/appsec/iast/aspects/test_ospath_aspects.py @@ -6,18 +6,18 @@ from ddtrace.appsec._iast._taint_tracking import OriginType from ddtrace.appsec._iast._taint_tracking import Source from ddtrace.appsec._iast._taint_tracking import TaintRange -from ddtrace.appsec._iast._taint_tracking import _aspect_ospathbasename -from ddtrace.appsec._iast._taint_tracking import _aspect_ospathdirname -from ddtrace.appsec._iast._taint_tracking import _aspect_ospathjoin -from ddtrace.appsec._iast._taint_tracking import _aspect_ospathnormcase -from ddtrace.appsec._iast._taint_tracking import _aspect_ospathsplit -from ddtrace.appsec._iast._taint_tracking import _aspect_ospathsplitext +from ddtrace.appsec._iast._taint_tracking.aspects import ospathbasename_aspect +from ddtrace.appsec._iast._taint_tracking.aspects import ospathdirname_aspect +from ddtrace.appsec._iast._taint_tracking.aspects import ospathjoin_aspect +from ddtrace.appsec._iast._taint_tracking.aspects import ospathnormcase_aspect +from ddtrace.appsec._iast._taint_tracking.aspects import ospathsplit_aspect +from ddtrace.appsec._iast._taint_tracking.aspects import ospathsplitext_aspect if sys.version_info >= (3, 12) or os.name == "nt": - from ddtrace.appsec._iast._taint_tracking import _aspect_ospathsplitdrive + from ddtrace.appsec._iast._taint_tracking.aspects import ospathsplitdrive_aspect if sys.version_info >= (3, 12): - from ddtrace.appsec._iast._taint_tracking import _aspect_ospathsplitroot + from ddtrace.appsec._iast._taint_tracking.aspects import ospathsplitroot_aspect from ddtrace.appsec._iast._taint_tracking import get_tainted_ranges from ddtrace.appsec._iast._taint_tracking import taint_pyobject @@ -36,7 +36,7 @@ def test_ospathjoin_first_arg_nottainted_noslash(): source_value="bar", source_origin=OriginType.PARAMETER, ) - res = _aspect_ospathjoin("root", tainted_foo, "nottainted", tainted_bar, "alsonottainted") + res = ospathjoin_aspect("root", tainted_foo, "nottainted", tainted_bar, "alsonottainted") assert res == "root/foo/nottainted/bar/alsonottainted" assert get_tainted_ranges(res) == [ TaintRange(5, 3, Source("test_ospath", "foo", OriginType.PARAMETER)), @@ -59,7 +59,7 @@ def test_ospathjoin_later_arg_tainted_with_slash_then_ignore_previous(): source_origin=OriginType.PARAMETER, ) - res = _aspect_ospathjoin("ignored", ignored_tainted_foo, "ignored_nottainted", tainted_slashbar, "alsonottainted") + res = ospathjoin_aspect("ignored", ignored_tainted_foo, "ignored_nottainted", tainted_slashbar, "alsonottainted") assert res == "/bar/alsonottainted" assert get_tainted_ranges(res) == [ TaintRange(0, 4, Source("test_ospath", "/bar", OriginType.PARAMETER)), @@ -81,7 +81,7 @@ def test_ospathjoin_first_arg_tainted_no_slash(): source_origin=OriginType.PARAMETER, ) - res = _aspect_ospathjoin(tainted_foo, "nottainted", tainted_bar, "alsonottainted") + res = ospathjoin_aspect(tainted_foo, "nottainted", tainted_bar, "alsonottainted") assert res == "foo/nottainted/bar/alsonottainted" assert get_tainted_ranges(res) == [ TaintRange(0, 3, Source("test_ospath", "foo", OriginType.PARAMETER)), @@ -104,7 +104,7 @@ def test_ospathjoin_first_arg_tainted_with_slash(): source_origin=OriginType.PARAMETER, ) - res = _aspect_ospathjoin(tainted_slashfoo, "nottainted", tainted_bar, "alsonottainted") + res = ospathjoin_aspect(tainted_slashfoo, "nottainted", tainted_bar, "alsonottainted") assert res == "/foo/nottainted/bar/alsonottainted" assert get_tainted_ranges(res) == [ TaintRange(0, 4, Source("test_ospath", "/foo", OriginType.PARAMETER)), @@ -113,11 +113,11 @@ def test_ospathjoin_first_arg_tainted_with_slash(): def test_ospathjoin_single_arg_nottainted(): - res = _aspect_ospathjoin("nottainted") + res = ospathjoin_aspect("nottainted") assert res == "nottainted" assert not get_tainted_ranges(res) - res = _aspect_ospathjoin("/nottainted") + res = ospathjoin_aspect("/nottainted") assert res == "/nottainted" assert not get_tainted_ranges(res) @@ -129,7 +129,7 @@ def test_ospathjoin_single_arg_tainted(): source_value="foo", source_origin=OriginType.PARAMETER, ) - res = _aspect_ospathjoin(tainted_foo) + res = ospathjoin_aspect(tainted_foo) assert res == "foo" assert get_tainted_ranges(res) == [TaintRange(0, 3, Source("test_ospath", "/foo", OriginType.PARAMETER))] @@ -139,7 +139,7 @@ def test_ospathjoin_single_arg_tainted(): source_value="/foo", source_origin=OriginType.PARAMETER, ) - res = _aspect_ospathjoin(tainted_slashfoo) + res = ospathjoin_aspect(tainted_slashfoo) assert res == "/foo" assert get_tainted_ranges(res) == [TaintRange(0, 4, Source("test_ospath", "/foo", OriginType.PARAMETER))] @@ -152,7 +152,7 @@ def test_ospathjoin_last_slash_nottainted(): source_origin=OriginType.PARAMETER, ) - res = _aspect_ospathjoin("root", tainted_foo, "/nottainted") + res = ospathjoin_aspect("root", tainted_foo, "/nottainted") assert res == "/nottainted" assert not get_tainted_ranges(res) @@ -171,18 +171,18 @@ def test_ospathjoin_last_slash_tainted(): source_value="/bar", source_origin=OriginType.PARAMETER, ) - res = _aspect_ospathjoin("root", tainted_foo, "nottainted", tainted_slashbar) + res = ospathjoin_aspect("root", tainted_foo, "nottainted", tainted_slashbar) assert res == "/bar" assert get_tainted_ranges(res) == [TaintRange(0, 4, Source("test_ospath", "/bar", OriginType.PARAMETER))] def test_ospathjoin_wrong_arg(): with pytest.raises(TypeError): - _ = _aspect_ospathjoin("root", 42, "foobar") + _ = ospathjoin_aspect("root", 42, "foobar") def test_ospathjoin_bytes_nottainted(): - res = _aspect_ospathjoin(b"nottainted", b"alsonottainted") + res = ospathjoin_aspect(b"nottainted", b"alsonottainted") assert res == b"nottainted/alsonottainted" @@ -193,7 +193,7 @@ def test_ospathjoin_bytes_tainted(): source_value=b"foo", source_origin=OriginType.PARAMETER, ) - res = _aspect_ospathjoin(tainted_foo, b"nottainted") + res = ospathjoin_aspect(tainted_foo, b"nottainted") assert res == b"foo/nottainted" assert get_tainted_ranges(res) == [TaintRange(0, 3, Source("test_ospath", b"foo", OriginType.PARAMETER))] @@ -204,23 +204,23 @@ def test_ospathjoin_bytes_tainted(): source_origin=OriginType.PARAMETER, ) - res = _aspect_ospathjoin(tainted_slashfoo, b"nottainted") + res = ospathjoin_aspect(tainted_slashfoo, b"nottainted") assert res == b"/foo/nottainted" assert get_tainted_ranges(res) == [TaintRange(0, 4, Source("test_ospath", b"/foo", OriginType.PARAMETER))] - res = _aspect_ospathjoin(b"nottainted_ignore", b"alsoignored", tainted_slashfoo) + res = ospathjoin_aspect(b"nottainted_ignore", b"alsoignored", tainted_slashfoo) assert res == b"/foo" assert get_tainted_ranges(res) == [TaintRange(0, 4, Source("test_ospath", b"/foo", OriginType.PARAMETER))] def test_ospathjoin_empty(): - res = _aspect_ospathjoin("") + res = ospathjoin_aspect("") assert res == "" def test_ospathjoin_noparams(): with pytest.raises(TypeError): - _ = _aspect_ospathjoin() + _ = ospathjoin_aspect() def test_ospathbasename_tainted_normal(): @@ -231,7 +231,7 @@ def test_ospathbasename_tainted_normal(): source_origin=OriginType.PARAMETER, ) - res = _aspect_ospathbasename(tainted_foobarbaz) + res = ospathbasename_aspect(tainted_foobarbaz) assert res == "baz" assert get_tainted_ranges(res) == [TaintRange(0, 3, Source("test_ospath", "/foo/bar/baz", OriginType.PARAMETER))] @@ -244,20 +244,20 @@ def test_ospathbasename_tainted_empty(): source_origin=OriginType.PARAMETER, ) - res = _aspect_ospathbasename(tainted_empty) + res = ospathbasename_aspect(tainted_empty) assert res == "" assert not get_tainted_ranges(res) def test_ospathbasename_nottainted(): - res = _aspect_ospathbasename("/foo/bar/baz") + res = ospathbasename_aspect("/foo/bar/baz") assert res == "baz" assert not get_tainted_ranges(res) def test_ospathbasename_wrong_arg(): with pytest.raises(TypeError): - _ = _aspect_ospathbasename(42) + _ = ospathbasename_aspect(42) def test_ospathbasename_bytes_tainted(): @@ -268,13 +268,13 @@ def test_ospathbasename_bytes_tainted(): source_origin=OriginType.PARAMETER, ) - res = _aspect_ospathbasename(tainted_foobarbaz) + res = ospathbasename_aspect(tainted_foobarbaz) assert res == b"baz" assert get_tainted_ranges(res) == [TaintRange(0, 3, Source("test_ospath", b"/foo/bar/baz", OriginType.PARAMETER))] def test_ospathbasename_bytes_nottainted(): - res = _aspect_ospathbasename(b"/foo/bar/baz") + res = ospathbasename_aspect(b"/foo/bar/baz") assert res == b"baz" assert not get_tainted_ranges(res) @@ -286,13 +286,13 @@ def test_ospathbasename_single_slash_tainted(): source_value="/", source_origin=OriginType.PARAMETER, ) - res = _aspect_ospathbasename(tainted_slash) + res = ospathbasename_aspect(tainted_slash) assert res == "" assert not get_tainted_ranges(res) def test_ospathbasename_nottainted_empty(): - res = _aspect_ospathbasename("") + res = ospathbasename_aspect("") assert res == "" assert not get_tainted_ranges(res) @@ -305,7 +305,7 @@ def test_ospathnormcase_tainted_normal(): source_origin=OriginType.PARAMETER, ) - res = _aspect_ospathnormcase(tainted_foobarbaz) + res = ospathnormcase_aspect(tainted_foobarbaz) assert res == "/foo/bar/baz" assert get_tainted_ranges(res) == [TaintRange(0, 12, Source("test_ospath", "/foo/bar/baz", OriginType.PARAMETER))] @@ -318,20 +318,20 @@ def test_ospathnormcase_tainted_empty(): source_origin=OriginType.PARAMETER, ) - res = _aspect_ospathnormcase(tainted_empty) + res = ospathnormcase_aspect(tainted_empty) assert res == "" assert not get_tainted_ranges(res) def test_ospathnormcase_nottainted(): - res = _aspect_ospathnormcase("/foo/bar/baz") + res = ospathnormcase_aspect("/foo/bar/baz") assert res == "/foo/bar/baz" assert not get_tainted_ranges(res) def test_ospathnormcase_wrong_arg(): with pytest.raises(TypeError): - _ = _aspect_ospathnormcase(42) + _ = ospathnormcase_aspect(42) def test_ospathnormcase_bytes_tainted(): @@ -342,13 +342,13 @@ def test_ospathnormcase_bytes_tainted(): source_origin=OriginType.PARAMETER, ) - res = _aspect_ospathnormcase(tainted_foobarbaz) + res = ospathnormcase_aspect(tainted_foobarbaz) assert res == b"/foo/bar/baz" assert get_tainted_ranges(res) == [TaintRange(0, 12, Source("test_ospath", b"/foo/bar/baz", OriginType.PARAMETER))] def test_ospathnormcase_bytes_nottainted(): - res = _aspect_ospathnormcase(b"/foo/bar/baz") + res = ospathnormcase_aspect(b"/foo/bar/baz") assert res == b"/foo/bar/baz" assert not get_tainted_ranges(res) @@ -360,13 +360,13 @@ def test_ospathnormcase_single_slash_tainted(): source_value="/", source_origin=OriginType.PARAMETER, ) - res = _aspect_ospathnormcase(tainted_slash) + res = ospathnormcase_aspect(tainted_slash) assert res == "/" assert get_tainted_ranges(res) == [TaintRange(0, 1, Source("test_ospath", "/", OriginType.PARAMETER))] def test_ospathnormcase_nottainted_empty(): - res = _aspect_ospathnormcase("") + res = ospathnormcase_aspect("") assert res == "" assert not get_tainted_ranges(res) @@ -379,7 +379,7 @@ def test_ospathdirname_tainted_normal(): source_origin=OriginType.PARAMETER, ) - res = _aspect_ospathdirname(tainted_foobarbaz) + res = ospathdirname_aspect(tainted_foobarbaz) assert res == "/foo/bar" assert get_tainted_ranges(res) == [TaintRange(0, 8, Source("test_ospath", "/foo/bar/baz", OriginType.PARAMETER))] @@ -392,20 +392,20 @@ def test_ospathdirname_tainted_empty(): source_origin=OriginType.PARAMETER, ) - res = _aspect_ospathdirname(tainted_empty) + res = ospathdirname_aspect(tainted_empty) assert res == "" assert not get_tainted_ranges(res) def test_ospathdirname_nottainted(): - res = _aspect_ospathdirname("/foo/bar/baz") + res = ospathdirname_aspect("/foo/bar/baz") assert res == "/foo/bar" assert not get_tainted_ranges(res) def test_ospathdirname_wrong_arg(): with pytest.raises(TypeError): - _ = _aspect_ospathdirname(42) + _ = ospathdirname_aspect(42) def test_ospathdirname_bytes_tainted(): @@ -416,13 +416,13 @@ def test_ospathdirname_bytes_tainted(): source_origin=OriginType.PARAMETER, ) - res = _aspect_ospathdirname(tainted_foobarbaz) + res = ospathdirname_aspect(tainted_foobarbaz) assert res == b"/foo/bar" assert get_tainted_ranges(res) == [TaintRange(0, 8, Source("test_ospath", b"/foo/bar/baz", OriginType.PARAMETER))] def test_ospathdirname_bytes_nottainted(): - res = _aspect_ospathdirname(b"/foo/bar/baz") + res = ospathdirname_aspect(b"/foo/bar/baz") assert res == b"/foo/bar" assert not get_tainted_ranges(res) @@ -434,13 +434,13 @@ def test_ospathdirname_single_slash_tainted(): source_value="/", source_origin=OriginType.PARAMETER, ) - res = _aspect_ospathdirname(tainted_slash) + res = ospathdirname_aspect(tainted_slash) assert res == "/" assert get_tainted_ranges(res) == [TaintRange(0, 1, Source("test_ospath", "/", OriginType.PARAMETER))] def test_ospathdirname_nottainted_empty(): - res = _aspect_ospathdirname("") + res = ospathdirname_aspect("") assert res == "" assert not get_tainted_ranges(res) @@ -453,7 +453,7 @@ def test_ospathsplit_tainted_normal(): source_origin=OriginType.PARAMETER, ) - res = _aspect_ospathsplit(tainted_foobarbaz) + res = ospathsplit_aspect(tainted_foobarbaz) assert res == ("/foo/bar", "baz") assert get_tainted_ranges(res[0]) == [TaintRange(0, 8, Source("test_ospath", "/foo/bar/baz", OriginType.PARAMETER))] assert get_tainted_ranges(res[1]) == [TaintRange(0, 3, Source("test_ospath", "/foo/bar/baz", OriginType.PARAMETER))] @@ -467,14 +467,14 @@ def test_ospathsplit_tainted_empty(): source_origin=OriginType.PARAMETER, ) - res = _aspect_ospathsplit(tainted_empty) + res = ospathsplit_aspect(tainted_empty) assert res == ("", "") assert not get_tainted_ranges(res[0]) assert not get_tainted_ranges(res[1]) def test_ospathsplit_nottainted(): - res = _aspect_ospathsplit("/foo/bar/baz") + res = ospathsplit_aspect("/foo/bar/baz") assert res == ("/foo/bar", "baz") assert not get_tainted_ranges(res[0]) assert not get_tainted_ranges(res[1]) @@ -482,7 +482,7 @@ def test_ospathsplit_nottainted(): def test_ospathsplit_wrong_arg(): with pytest.raises(TypeError): - _ = _aspect_ospathsplit(42) + _ = ospathsplit_aspect(42) def test_ospathsplit_bytes_tainted(): @@ -493,7 +493,7 @@ def test_ospathsplit_bytes_tainted(): source_origin=OriginType.PARAMETER, ) - res = _aspect_ospathsplit(tainted_foobarbaz) + res = ospathsplit_aspect(tainted_foobarbaz) assert res == (b"/foo/bar", b"baz") assert get_tainted_ranges(res[0]) == [ TaintRange(0, 8, Source("test_ospath", b"/foo/bar/baz", OriginType.PARAMETER)) @@ -504,7 +504,7 @@ def test_ospathsplit_bytes_tainted(): def test_ospathsplit_bytes_nottainted(): - res = _aspect_ospathsplit(b"/foo/bar/baz") + res = ospathsplit_aspect(b"/foo/bar/baz") assert res == (b"/foo/bar", b"baz") assert not get_tainted_ranges(res[0]) assert not get_tainted_ranges(res[1]) @@ -517,14 +517,14 @@ def test_ospathsplit_single_slash_tainted(): source_value="/", source_origin=OriginType.PARAMETER, ) - res = _aspect_ospathsplit(tainted_slash) + res = ospathsplit_aspect(tainted_slash) assert res == ("/", "") assert get_tainted_ranges(res[0]) == [TaintRange(0, 1, Source("test_ospath", "/", OriginType.PARAMETER))] assert not get_tainted_ranges(res[1]) def test_ospathsplit_nottainted_empty(): - res = _aspect_ospathsplit("") + res = ospathsplit_aspect("") assert res == ("", "") assert not get_tainted_ranges(res[0]) assert not get_tainted_ranges(res[1]) @@ -538,7 +538,7 @@ def test_ospathsplitext_tainted_normal(): source_origin=OriginType.PARAMETER, ) - res = _aspect_ospathsplitext(tainted_foobarbaz) + res = ospathsplitext_aspect(tainted_foobarbaz) assert res == ("/foo/bar/baz", ".jpg") assert get_tainted_ranges(res[0]) == [ TaintRange(0, 12, Source("test_ospath", "/foo/bar/baz.jpg", OriginType.PARAMETER)) @@ -557,7 +557,7 @@ def test_ospathsplitroot_tainted_normal(): source_origin=OriginType.PARAMETER, ) - res = _aspect_ospathsplitroot(tainted_foobarbaz) + res = ospathsplitroot_aspect(tainted_foobarbaz) assert res == ("", "/", "foo/bar/baz") assert not get_tainted_ranges(res[0]) assert get_tainted_ranges(res[1]) == [TaintRange(0, 1, Source("test_ospath", "/foo/bar/baz", OriginType.PARAMETER))] @@ -575,7 +575,7 @@ def test_ospathsplitroot_tainted_doble_initial_slash(): source_origin=OriginType.PARAMETER, ) - res = _aspect_ospathsplitroot(tainted_foobarbaz) + res = ospathsplitroot_aspect(tainted_foobarbaz) assert res == ("", "//", "foo/bar/baz") assert not get_tainted_ranges(res[0]) assert get_tainted_ranges(res[1]) == [ @@ -595,7 +595,7 @@ def test_ospathsplitroot_tainted_triple_initial_slash(): source_origin=OriginType.PARAMETER, ) - res = _aspect_ospathsplitroot(tainted_foobarbaz) + res = ospathsplitroot_aspect(tainted_foobarbaz) assert res == ("", "/", "//foo/bar/baz") assert not get_tainted_ranges(res[0]) assert get_tainted_ranges(res[1]) == [ @@ -615,7 +615,7 @@ def test_ospathsplitroot_tainted_empty(): source_origin=OriginType.PARAMETER, ) - res = _aspect_ospathsplitroot(tainted_empty) + res = ospathsplitroot_aspect(tainted_empty) assert res == ("", "", "") for i in res: assert not get_tainted_ranges(i) @@ -623,7 +623,7 @@ def test_ospathsplitroot_tainted_empty(): @pytest.mark.skipif(sys.version_info < (3, 12), reason="Requires Python 3.12") def test_ospathsplitroot_nottainted(): - res = _aspect_ospathsplitroot("/foo/bar/baz") + res = ospathsplitroot_aspect("/foo/bar/baz") assert res == ("", "/", "foo/bar/baz") for i in res: assert not get_tainted_ranges(i) @@ -632,7 +632,7 @@ def test_ospathsplitroot_nottainted(): @pytest.mark.skipif(sys.version_info < (3, 12), reason="Requires Python 3.12") def test_ospathsplitroot_wrong_arg(): with pytest.raises(TypeError): - _ = _aspect_ospathsplitroot(42) + _ = ospathsplitroot_aspect(42) @pytest.mark.skipif(sys.version_info < (3, 12), reason="Requires Python 3.12") @@ -644,7 +644,7 @@ def test_ospathsplitroot_bytes_tainted(): source_origin=OriginType.PARAMETER, ) - res = _aspect_ospathsplitroot(tainted_foobarbaz) + res = ospathsplitroot_aspect(tainted_foobarbaz) assert res == (b"", b"/", b"foo/bar/baz") assert not get_tainted_ranges(res[0]) assert get_tainted_ranges(res[1]) == [ @@ -657,7 +657,7 @@ def test_ospathsplitroot_bytes_tainted(): @pytest.mark.skipif(sys.version_info < (3, 12), reason="Requires Python 3.12") def test_ospathsplitroot_bytes_nottainted(): - res = _aspect_ospathsplitroot(b"/foo/bar/baz") + res = ospathsplitroot_aspect(b"/foo/bar/baz") assert res == (b"", b"/", b"foo/bar/baz") for i in res: assert not get_tainted_ranges(i) @@ -671,7 +671,7 @@ def tets_ospathsplitroot_single_slash_tainted(): source_value="/", source_origin=OriginType.PARAMETER, ) - res = _aspect_ospathsplitroot(tainted_slash) + res = ospathsplitroot_aspect(tainted_slash) assert res == ("", "/", "") assert not get_tainted_ranges(res[0]) assert get_tainted_ranges(res[1]) == [TaintRange(0, 1, Source("test_ospath", "/", OriginType.PARAMETER))] @@ -687,7 +687,7 @@ def test_ospathsplitdrive_tainted_normal(): source_origin=OriginType.PARAMETER, ) - res = _aspect_ospathsplitdrive(tainted_foobarbaz) + res = ospathsplitdrive_aspect(tainted_foobarbaz) assert res == ("", "/foo/bar/baz") assert not get_tainted_ranges(res[0]) assert get_tainted_ranges(res[1]) == [ @@ -704,7 +704,7 @@ def test_ospathsplitdrive_tainted_empty(): source_origin=OriginType.PARAMETER, ) - res = _aspect_ospathsplitdrive(tainted_empty) + res = ospathsplitdrive_aspect(tainted_empty) assert res == ("", "") for i in res: assert not get_tainted_ranges(i) From 66b96e9693df5ae809da9eb8212230a9a4738890 Mon Sep 17 00:00:00 2001 From: Romain Komorn <136473744+romainkomorndatadog@users.noreply.github.com> Date: Wed, 22 May 2024 10:57:45 +0100 Subject: [PATCH 093/104] chore(ci_visibility): introduce experimental internal coverage collector (#8727) Introduces the `ModuleCodeCollector` which collects coverage and executable lines for imported modules. The collector has two modes, one that stores executed lines on the singleton instance, and one that uses context variables and a context manager. This also introduces changes to the `pytest` integration as well as the `CIVisibility` service's use of coverage to feature-flag using the new module collector. The features are gated behind `_DD_USE_INTERNAL_COVERAGE` and `_DD_COVER_SESSION` (which introduces a new `coverage run` like behavior). There are no unit tests though the overall use of the feature flags has been tested quite extensively in the process of collecting performance data. There are no release notes since this is an entirely undocumented feature for the moment. ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. - [x] If change touches code that signs or publishes builds or packages, or handles credentials of any kind, I've requested a review from `@DataDog/security-design-and-guidance`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Gabriele N. Tornetta Co-authored-by: Gabriele N. Tornetta --- .github/CODEOWNERS | 1 + ddtrace/contrib/pytest/_plugin_v1.py | 19 ++ ddtrace/contrib/pytest/plugin.py | 41 ++++ ddtrace/internal/ci_visibility/coverage.py | 42 ++++ ddtrace/internal/coverage/__init__.py | 0 ddtrace/internal/coverage/_native.c | 61 +++++ ddtrace/internal/coverage/_native.pyi | 3 + ddtrace/internal/coverage/code.py | 237 +++++++++++++++++++ ddtrace/internal/coverage/instrumentation.py | 37 +++ ddtrace/internal/coverage/report.py | 208 ++++++++++++++++ ddtrace/internal/coverage/util.py | 20 ++ ddtrace/internal/module.py | 40 +++- setup.py | 7 + tests/.suitespec.json | 4 + 14 files changed, 716 insertions(+), 4 deletions(-) create mode 100644 ddtrace/internal/coverage/__init__.py create mode 100644 ddtrace/internal/coverage/_native.c create mode 100644 ddtrace/internal/coverage/_native.pyi create mode 100644 ddtrace/internal/coverage/code.py create mode 100644 ddtrace/internal/coverage/instrumentation.py create mode 100644 ddtrace/internal/coverage/report.py create mode 100644 ddtrace/internal/coverage/util.py diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9183c3fb92d..a4285b92563 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -40,6 +40,7 @@ ddtrace/ext/ci_visibility @DataDog/ci-app-libraries ddtrace/ext/test.py @DataDog/ci-app-libraries ddtrace/internal/ci_visibility @DataDog/ci-app-libraries ddtrace/internal/codeowners.py @DataDog/apm-core-python @datadog/ci-app-libraries +ddtrace/internal/coverage @DataDog/apm-core-python @datadog/ci-app-libraries @Datadog/debugger-python tests/internal/test_codeowners.py @datadog/ci-app-libraries tests/ci_visibility @DataDog/ci-app-libraries tests/tracer/test_ci.py @DataDog/ci-app-libraries diff --git a/ddtrace/contrib/pytest/_plugin_v1.py b/ddtrace/contrib/pytest/_plugin_v1.py index d9e122ccec2..c4c5648a086 100644 --- a/ddtrace/contrib/pytest/_plugin_v1.py +++ b/ddtrace/contrib/pytest/_plugin_v1.py @@ -13,6 +13,7 @@ """ from doctest import DocTest import json +import os import re from typing import Dict # noqa:F401 @@ -46,6 +47,7 @@ from ddtrace.internal.ci_visibility.constants import SUITE_ID as _SUITE_ID from ddtrace.internal.ci_visibility.constants import SUITE_TYPE as _SUITE_TYPE from ddtrace.internal.ci_visibility.constants import TEST +from ddtrace.internal.ci_visibility.coverage import USE_DD_COVERAGE from ddtrace.internal.ci_visibility.coverage import _module_has_dd_coverage_enabled from ddtrace.internal.ci_visibility.coverage import _report_coverage_to_span from ddtrace.internal.ci_visibility.coverage import _start_coverage @@ -59,13 +61,19 @@ from ddtrace.internal.ci_visibility.utils import get_relative_or_absolute_path_for_path from ddtrace.internal.ci_visibility.utils import take_over_logger_stream_handler from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.coverage.code import ModuleCodeCollector from ddtrace.internal.logger import get_logger +from ddtrace.internal.utils.formats import asbool log = get_logger(__name__) _global_skipped_elements = 0 +# COVER_SESSION is an experimental feature flag that provides full coverage (similar to coverage run), and is an +# experimental feature. It currently significantly increases test import time and should not be used. +COVER_SESSION = asbool(os.environ.get("_DD_COVER_SESSION", "false")) + def _is_pytest_8_or_later(): if hasattr(pytest, "version_tuple"): @@ -859,3 +867,14 @@ def pytest_ddtrace_get_item_test_name(item): if item.config.getoption("ddtrace-include-class-name") or item.config.getini("ddtrace-include-class-name"): return "%s.%s" % (item.cls.__name__, item.name) return item.name + + @staticmethod + @pytest.hookimpl(trylast=True) + def pytest_terminal_summary(terminalreporter, exitstatus, config): + # Reports coverage if experimental session-level coverage is enabled. + if USE_DD_COVERAGE and COVER_SESSION: + ModuleCodeCollector.report() + try: + ModuleCodeCollector.write_json_report_to_file("dd_coverage.json") + except Exception: + log.debug("Failed to write coverage report to file", exc_info=True) diff --git a/ddtrace/contrib/pytest/plugin.py b/ddtrace/contrib/pytest/plugin.py index 059f500addf..5da501abe40 100644 --- a/ddtrace/contrib/pytest/plugin.py +++ b/ddtrace/contrib/pytest/plugin.py @@ -11,6 +11,7 @@ expected failures. """ +import os from typing import Dict # noqa:F401 import pytest @@ -22,6 +23,21 @@ PATCH_ALL_HELP_MSG = "Call ddtrace.patch_all before running tests." +def _is_enabled_early(early_config): + """Hackily checks if the ddtrace plugin is enabled before the config is fully populated. + + This is necessary because the module watchdog for coverage collectio needs to be enabled as early as possible. + """ + if ( + "--no-ddtrace" in early_config.invocation_params.args + or early_config.getini("ddtrace") is False + or early_config.getini("no-ddtrace") + ): + return False + + return "--ddtrace" in early_config.invocation_params.args or early_config.getini("ddtrace") + + def is_enabled(config): """Check if the ddtrace plugin is enabled.""" return (config.getoption("ddtrace") or config.getini("ddtrace")) and not config.getoption("no-ddtrace") @@ -69,6 +85,31 @@ def pytest_addoption(parser): parser.addini("ddtrace-include-class-name", DDTRACE_INCLUDE_CLASS_HELP_MSG, type="bool") +def pytest_load_initial_conftests(early_config, parser, args): + if _is_enabled_early(early_config): + # Enables experimental use of ModuleCodeCollector for coverage collection. + from ddtrace.internal.ci_visibility.coverage import USE_DD_COVERAGE + from ddtrace.internal.logger import get_logger + from ddtrace.internal.utils.formats import asbool + + log = get_logger(__name__) + + COVER_SESSION = asbool(os.environ.get("_DD_COVER_SESSION", "false")) + + if USE_DD_COVERAGE: + from ddtrace.internal.coverage.code import ModuleCodeCollector + + if not ModuleCodeCollector.is_installed(): + ModuleCodeCollector.install() + if COVER_SESSION: + ModuleCodeCollector.start_coverage() + else: + if COVER_SESSION: + log.warning( + "_DD_COVER_SESSION must be used with _DD_USE_INTERNAL_COVERAGE but not DD_CIVISIBILITY_ITR_ENABLED" + ) + + def pytest_configure(config): config.addinivalue_line("markers", "dd_tags(**kwargs): add tags to current span") if is_enabled(config): diff --git a/ddtrace/internal/ci_visibility/coverage.py b/ddtrace/internal/ci_visibility/coverage.py index b58566105ef..36aacc01227 100644 --- a/ddtrace/internal/ci_visibility/coverage.py +++ b/ddtrace/internal/ci_visibility/coverage.py @@ -1,10 +1,12 @@ from itertools import groupby import json +import os from typing import Dict # noqa:F401 from typing import Iterable # noqa:F401 from typing import List # noqa:F401 from typing import Optional # noqa:F401 from typing import Tuple # noqa:F401 +from typing import Union # noqa:F401 import ddtrace from ddtrace.internal.ci_visibility.constants import COVERAGE_TAG_NAME @@ -16,12 +18,18 @@ from ddtrace.internal.ci_visibility.telemetry.coverage import record_code_coverage_finished from ddtrace.internal.ci_visibility.telemetry.coverage import record_code_coverage_started from ddtrace.internal.ci_visibility.utils import get_relative_or_absolute_path_for_path +from ddtrace.internal.coverage.code import ModuleCodeCollector from ddtrace.internal.logger import get_logger +from ddtrace.internal.utils.formats import asbool log = get_logger(__name__) _global_relative_file_paths_for_cov: Dict[str, Dict[str, str]] = {} +# This feature-flags experimental collection of code coverage via our internal ModuleCodeCollector. +# It is disabled by default because it is not production-ready. +USE_DD_COVERAGE = asbool(os.environ.get("_DD_USE_INTERNAL_COVERAGE", "false")) + try: from coverage import Coverage from coverage import version_info as coverage_version @@ -52,12 +60,20 @@ def _initialize_coverage(root_dir): def _start_coverage(root_dir: str): + # Experimental feature to use internal coverage collection + if USE_DD_COVERAGE: + ctx = ModuleCodeCollector.CollectInContext() + return ctx coverage = _initialize_coverage(root_dir) coverage.start() return coverage def _stop_coverage(module): + # Experimental feature to use internal coverage collection + if USE_DD_COVERAGE: + module._dd_coverage.__exit__() + return if _module_has_dd_coverage_enabled(module): module._dd_coverage.stop() module._dd_coverage.erase() @@ -65,6 +81,9 @@ def _stop_coverage(module): def _module_has_dd_coverage_enabled(module, silent_mode: bool = False) -> bool: + # Experimental feature to use internal coverage collection + if USE_DD_COVERAGE: + return hasattr(module, "_dd_coverage") if not hasattr(module, "_dd_coverage"): if not silent_mode: log.warning("Datadog Coverage has not been initiated") @@ -84,6 +103,13 @@ def _switch_coverage_context( coverage_data: Coverage, unique_test_name: str, framework: Optional[TEST_FRAMEWORKS] = None ): record_code_coverage_started(COVERAGE_LIBRARY.COVERAGEPY, framework) + # Experimental feature to use internal coverage collection + if isinstance(coverage_data, ModuleCodeCollector.CollectInContext): + if USE_DD_COVERAGE: + # In this case, coverage_data is the context manager supplied by ModuleCodeCollector.CollectInContext + coverage_data.__enter__() + return + if not _coverage_has_valid_data(coverage_data, silent_mode=True): return coverage_data._collector.data.clear() # type: ignore[union-attr] @@ -97,6 +123,22 @@ def _switch_coverage_context( def _report_coverage_to_span( coverage_data: Coverage, span: ddtrace.Span, root_dir: str, framework: Optional[TEST_FRAMEWORKS] = None ): + # Experimental feature to use internal coverage collection + if isinstance(coverage_data, ModuleCodeCollector.CollectInContext): + if USE_DD_COVERAGE: + # In this case, coverage_data is the context manager supplied by ModuleCodeCollector.CollectInContext + files = ModuleCodeCollector.report_seen_lines() + if not files: + return + span.set_tag_str( + COVERAGE_TAG_NAME, + json.dumps({"files": files}), + ) + record_code_coverage_finished(COVERAGE_LIBRARY.COVERAGEPY, framework) + coverage_data.__exit__(None, None, None) + + return + span_id = str(span.trace_id) if not _coverage_has_valid_data(coverage_data): record_code_coverage_error() diff --git a/ddtrace/internal/coverage/__init__.py b/ddtrace/internal/coverage/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ddtrace/internal/coverage/_native.c b/ddtrace/internal/coverage/_native.c new file mode 100644 index 00000000000..52583586d50 --- /dev/null +++ b/ddtrace/internal/coverage/_native.c @@ -0,0 +1,61 @@ +#define PY_SSIZE_T_CLEAN +#include + +#if PY_VERSION_HEX < 0x030c0000 +#if defined __GNUC__ && defined HAVE_STD_ATOMIC +#undef HAVE_STD_ATOMIC +#endif +#endif + +// ---------------------------------------------------------------------------- +static PyObject* +replace_in_tuple(PyObject* m, PyObject* args) +{ + PyObject* tuple = NULL; + PyObject* item = NULL; + PyObject* replacement = NULL; + + if (!PyArg_ParseTuple(args, "O!OO", &PyTuple_Type, &tuple, &item, &replacement)) + return NULL; + + for (Py_ssize_t i = 0; i < PyTuple_Size(tuple); i++) { + PyObject* current = PyTuple_GetItem(tuple, i); + if (current == item) { + Py_DECREF(current); + // !!! DANGER !!! + PyTuple_SET_ITEM(tuple, i, replacement); + Py_INCREF(replacement); + } + } + + Py_RETURN_NONE; +} + +// ---------------------------------------------------------------------------- +static PyMethodDef native_methods[] = { + { "replace_in_tuple", replace_in_tuple, METH_VARARGS, "Replace an item in a tuple." }, + { NULL, NULL, 0, NULL } /* Sentinel */ +}; + +// ---------------------------------------------------------------------------- +static struct PyModuleDef nativemodule = { + PyModuleDef_HEAD_INIT, + "_native", /* name of module */ + NULL, /* module documentation, may be NULL */ + -1, /* size of per-interpreter state of the module, + or -1 if the module keeps state in global variables. */ + native_methods, +}; + +// ---------------------------------------------------------------------------- +PyMODINIT_FUNC +PyInit__native(void) +{ + PyObject* m; + + m = PyModule_Create(&nativemodule); + if (m == NULL) + return NULL; + + return m; +} diff --git a/ddtrace/internal/coverage/_native.pyi b/ddtrace/internal/coverage/_native.pyi new file mode 100644 index 00000000000..aceb41abe76 --- /dev/null +++ b/ddtrace/internal/coverage/_native.pyi @@ -0,0 +1,3 @@ +import typing as t + +def replace_in_tuple(tup: tuple, item: t.Any, replacement: t.Any) -> None: ... diff --git a/ddtrace/internal/coverage/code.py b/ddtrace/internal/coverage/code.py new file mode 100644 index 00000000000..f6c126a3f59 --- /dev/null +++ b/ddtrace/internal/coverage/code.py @@ -0,0 +1,237 @@ +from collections import defaultdict +from collections import deque +from types import CodeType +from types import ModuleType +import typing as t + +from ddtrace.internal.compat import Path +from ddtrace.internal.coverage._native import replace_in_tuple +from ddtrace.internal.coverage.instrumentation import instrument_all_lines +from ddtrace.internal.coverage.report import get_json_report +from ddtrace.internal.coverage.report import print_coverage_report +from ddtrace.internal.coverage.util import collapse_ranges +from ddtrace.internal.module import BaseModuleWatchdog +from ddtrace.vendor.contextvars import ContextVar + + +CWD = Path.cwd() + +_original_exec = exec + +ctx_covered = ContextVar("ctx_covered", default=None) +ctx_coverage_enabed = ContextVar("ctx_coverage_enabled", default=False) + + +def collect_code_objects(code: CodeType) -> t.Iterator[t.Tuple[CodeType, t.Optional[CodeType]]]: + # Topological sorting + q = deque([code]) + g = {} + p = {} + leaves: t.Deque[CodeType] = deque() + + # Build the graph and the parent map + while q: + c = q.popleft() + new_codes = g[c] = {_ for _ in c.co_consts if isinstance(_, CodeType)} + if not new_codes: + leaves.append(c) + continue + for new_code in new_codes: + p[new_code] = c + q.extend(new_codes) + + # Yield the code objects in topological order + while leaves: + c = leaves.popleft() + parent = p.get(c) + yield c, parent + if parent is not None: + children = g[parent] + children.remove(c) + if not children: + leaves.append(parent) + + +class ModuleCodeCollector(BaseModuleWatchdog): + _instance: t.Optional["ModuleCodeCollector"] = None + + def __init__(self): + super().__init__() + self.seen = set() + self.coverage_enabled = False + self.lines = defaultdict(set) + self.covered = defaultdict(set) + + # Replace the built-in exec function with our own in the pytest globals + try: + import _pytest.assertion.rewrite as par + + par.exec = self._exec + except ImportError: + pass + + def hook(self, arg): + path, line = arg + if self.coverage_enabled: + lines = self.covered[path] + if line not in lines: + # This line has already been covered + lines.add(line) + + if ctx_coverage_enabed.get(): + ctx_lines = ctx_covered.get()[path] + if line not in ctx_lines: + ctx_lines.add(line) + + @classmethod + def report(cls, ignore_nocover: bool = False): + if cls._instance is None: + return + instance: ModuleCodeCollector = cls._instance + + executable_lines = instance.lines + covered_lines = instance._get_covered_lines() + + print_coverage_report(executable_lines, covered_lines, ignore_nocover=ignore_nocover) + + @classmethod + def write_json_report_to_file(cls, filename: str, ignore_nocover: bool = False): + if cls._instance is None: + return + instance: ModuleCodeCollector = cls._instance + + executable_lines = instance.lines + covered_lines = instance._get_covered_lines() + + with open(filename, "w") as f: + f.write(get_json_report(executable_lines, covered_lines, ignore_nocover=ignore_nocover)) + + def _get_covered_lines(self) -> t.Dict[str, t.Set[int]]: + if ctx_coverage_enabed.get(False): + return ctx_covered.get() + return self.covered + + class CollectInContext: + def __enter__(self): + ctx_covered.set(defaultdict(set)) + ctx_coverage_enabed.set(True) + + def __exit__(self, *args, **kwargs): + ctx_coverage_enabed.set(False) + + @classmethod + def start_coverage(cls): + if cls._instance is None: + return + cls._instance.coverage_enabled = True + + @classmethod + def stop_coverage(cls): + if cls._instance is None: + return + cls._instance.coverage_enabled = False + + @classmethod + def coverage_enabled(cls): + if ctx_coverage_enabed.get(): + return True + if cls._instance is None: + return False + return cls._instance.coverage_enabled + + @classmethod + def report_seen_lines(cls): + """Generate the same data as expected by ddtrace.ci_visibility.coverage.build_payload: + + if input_path is provided, filter files to only include that path, and make it relative to said path + + "files": [ + { + "filename": , + "segments": [ + [Int, Int, Int, Int, Int], # noqa:F401 + ] + }, + ... + ] + """ + if cls._instance is None: + return [] + files = [] + covered = cls._instance._get_covered_lines() + + for path, lines in covered.items(): + sorted_lines = sorted(lines) + collapsed_ranges = collapse_ranges(sorted_lines) + file_segments = [] + for file_segment in collapsed_ranges: + file_segments.append([file_segment[0], 0, file_segment[1], 0, -1]) + files.append({"filename": path, "segments": file_segments}) + + return files + + def transform(self, code: CodeType, _module: ModuleType) -> CodeType: + code_path = Path(code.co_filename).resolve() + # TODO: Remove hardcoded paths + if not code_path.is_relative_to(CWD): + # Not a code object we want to instrument + return code + + # Recursively instrument nested code objects, in topological order + # DEV: We need to make a list of the code objects because when we start + # mutating the parent code objects, the hashes maintained by the + # generator will be invalidated. + for nested_code, parent_code in list(collect_code_objects(code)): + # Instrument the code object + new_code = self.instrument_code(nested_code) + + # If it has a parent, update the parent's co_consts to point to the + # new code object. + if parent_code is not None: + replace_in_tuple(parent_code.co_consts, nested_code, new_code) + + return new_code + + def after_import(self, _module: ModuleType) -> None: + pass + + def instrument_code(self, code: CodeType) -> CodeType: + # Avoid instrumenting the same code object multiple times + if code in self.seen: + return code + self.seen.add(code) + + path = str(Path(code.co_filename).resolve().relative_to(CWD)) + + new_code, lines = instrument_all_lines(code, self.hook, path) + + # Keep note of all the lines that have been instrumented. These will be + # the ones that can be covered. + self.lines[path] |= lines + + return new_code + + def _exec(self, _object, _globals=None, _locals=None, **kwargs): + # The pytest module loader doesn't implement a get_code method so we + # need to intercept the loading of test modules by wrapping around the + # exec built-in function. + new_object = ( + self.transform(_object, None) + if isinstance(_object, CodeType) and _object.co_name == "" + else _object + ) + + # Execute the module before calling the after_import hook + _original_exec(new_object, _globals, _locals, **kwargs) + + @classmethod + def uninstall(cls) -> None: + # Restore the original exec function + try: + import _pytest.assertion.rewrite as par + + par.exec = _original_exec + except ImportError: + pass + + return super().uninstall() diff --git a/ddtrace/internal/coverage/instrumentation.py b/ddtrace/internal/coverage/instrumentation.py new file mode 100644 index 00000000000..63d778cd09b --- /dev/null +++ b/ddtrace/internal/coverage/instrumentation.py @@ -0,0 +1,37 @@ +from types import CodeType +import typing as t + +from bytecode import Bytecode + +from ddtrace.internal.injection import INJECTION_ASSEMBLY +from ddtrace.internal.injection import HookType + + +def instrument_all_lines(code: CodeType, hook: HookType, path: str) -> t.Tuple[CodeType, t.Set[int]]: + abstract_code = Bytecode.from_code(code) + + lines = set() + + last_lineno = None + for i, instr in enumerate(abstract_code): + try: + if instr.lineno == last_lineno: + continue + + last_lineno = instr.lineno + if last_lineno is None: + continue + + if instr.name in ("NOP", "RESUME"): + continue + + # Inject the hook at the beginning of the line + abstract_code[i:i] = INJECTION_ASSEMBLY.bind(dict(hook=hook, arg=(path, last_lineno)), lineno=last_lineno) + + # Track the line number + lines.add(last_lineno) + except AttributeError: + # pseudo-instruction (e.g. label) + pass + + return abstract_code.to_code(), lines diff --git a/ddtrace/internal/coverage/report.py b/ddtrace/internal/coverage/report.py new file mode 100644 index 00000000000..870886994d2 --- /dev/null +++ b/ddtrace/internal/coverage/report.py @@ -0,0 +1,208 @@ +import ast +import json +import linecache +import os +import re +import typing as t + +from ddtrace.internal.coverage.util import collapse_ranges + + +try: + w, _ = os.get_terminal_size() +except OSError: + w = 80 + +NOCOVER_PRAGMA_RE = re.compile(r"^\s*(?P.*)\s*#.*\s+pragma\s*:\s*no\s?cover.*$") + +ast_cache: t.Dict[str, t.Any] = {} + + +def _get_ast_for_path(path: str): + if path not in ast_cache: + with open(path, "r") as f: + file_src = f.read() + ast_cache[path] = ast.parse(file_src) + return ast_cache[path] + + +def find_statement_for_line(node, line): + if hasattr(node, "body"): + for child_node in node.body: + found_node = find_statement_for_line(child_node, line) + if found_node is not None: + return found_node + + # If the start and end line numbers are the same, we're (almost certainly) dealing with some kind of + # statement instead of the sort of block statements we're looking for. + if node.lineno == node.end_lineno: + return None + + if node.lineno <= line <= node.end_lineno: + return node + + return None + + +def no_cover(path, src_line) -> t.Optional[t.Tuple[int, int]]: + """Returns the start and end lines of statements to ignore the line includes pragma nocover. + + If the line ends with a :, parse the AST and return the block the line belongs to. + """ + text = linecache.getline(path, src_line).strip() + matches = NOCOVER_PRAGMA_RE.match(text) + if matches: + if matches["command"].strip().endswith(":"): + parsed = _get_ast_for_path(path) + statement = find_statement_for_line(parsed, src_line) + if statement is not None: + return statement.lineno, statement.end_lineno + # We shouldn't get here, in theory, but if we do, let's not consider anything uncovered. + return None + # If our line does not end in ':', assume it's just one line that needs to be removed + return src_line, src_line + return None + + +def print_coverage_report(executable_lines, covered_lines, ignore_nocover=False): + total_executable_lines = 0 + total_covered_lines = 0 + total_missed_lines = 0 + n = max(len(path) for path in executable_lines) + 4 + + covered_lines = covered_lines + + # Title + print(" DATADOG LINE COVERAGE REPORT ".center(w, "=")) + + # Header + print(f"{'PATH':<{n}}{'LINES':>8}{'MISSED':>8} {'COVERED':>8} MISSED LINES") + print("-" * (w)) + + for path, orig_lines in sorted(executable_lines.items()): + path_lines = orig_lines.copy() + path_covered = covered_lines[path].copy() + if not ignore_nocover: + for line in orig_lines: + # We may have already deleted this line due to no_cover + if line not in path_lines and line not in path_covered: + continue + no_cover_lines = no_cover(path, line) + if no_cover_lines: + for no_cover_line in range(no_cover_lines[0], no_cover_lines[1] + 1): + path_lines.discard(no_cover_line) + path_covered.discard(no_cover_line) + + n_lines = len(path_lines) + n_covered = len(path_covered) + n_missed = n_lines - n_covered + total_executable_lines += n_lines + total_covered_lines += n_covered + total_missed_lines += n_missed + if n_covered == 0: + continue + missed_ranges = collapse_ranges(sorted(path_lines - path_covered)) + missed = ",".join([f"{start}-{end}" if start != end else str(start) for start, end in missed_ranges]) + missed_str = f" [{missed}]" if missed else "" + print(f"{path:{n}s}{n_lines:>8}{n_missed:>8}{int(n_covered / n_lines * 100):>8}%{missed_str}") + print("-" * (w)) + total_covered_percent = int((total_covered_lines / total_executable_lines) * 100) + print(f"{'TOTAL':<{n}}{total_executable_lines:>8}{total_missed_lines:>8}{total_covered_percent:>8}%") + print() + + +def get_json_report(executable_lines, covered_lines, ignore_nocover=False): + """Writes a JSON-formatted coverage report similar in structure to coverage.py 's JSON report, but only + containing a subset (namely file-level executed and missing lines). + + { + "files": { + "path/to/file.py": { + "executed_lines": [1, 2, 3, 4, 11, 12, 13, ...], + "missing_lines": [5, 6, 7, 15, 16, 17, ...] + }, + ... + } + } + + """ + output = {"files": {}} + + for path, orig_lines in sorted(executable_lines.items()): + path_lines = orig_lines.copy() + path_covered = covered_lines[path].copy() + if not ignore_nocover: + for line in orig_lines: + # We may have already deleted this line due to no_cover + if line not in path_lines and line not in path_covered: + continue + no_cover_lines = no_cover(path, line) + if no_cover_lines: + for no_cover_line in range(no_cover_lines[0], no_cover_lines[1] + 1): + path_lines.discard(no_cover_line) + path_covered.discard(no_cover_line) + + output["files"][path] = { + "executed_lines": sorted(list(path_covered)), + "missing_lines": sorted(list(path_lines - path_covered)), + } + + return json.dumps(output) + + +def compare_coverage_reports(coverage_py_filename: str, dd_coverage_filename: str) -> t.Dict[str, t.Any]: + """Compare two JSON-formatted coverage reports and return a dictionary of the differences.""" + with open(coverage_py_filename, "r") as coverage_py_f: + coverage_py_data = json.load(coverage_py_f) + + with open(dd_coverage_filename, "r") as dd_coverage_f: + dd_coverage_data = json.load(dd_coverage_f) + + compared_data: t.Dict[str, t.Any] = { + "coverage_py_missed_files": [f for f in dd_coverage_data["files"] if f not in coverage_py_data["files"]], + "coverage_py_missed_executed_lines": {}, + "coverage_py_missed_missing_lines": {}, + "dd_coverage_missed_files": [ + f + for f in coverage_py_data["files"] + if len(coverage_py_data["files"][f]["executed_lines"]) > 0 and f not in dd_coverage_data["files"] + ], + "dd_coverage_missed_executed_lines": {}, + "dd_coverage_missed_missing_lines": {}, + } + + # Treat coverage.py as "source of truth" when comparing lines + for path in coverage_py_data["files"].keys() & dd_coverage_data["files"].keys(): + dd_coverage_missed_executed_lines = sorted( + set(coverage_py_data["files"][path]["executed_lines"]) + - set(coverage_py_data["files"][path]["excluded_lines"]) # Lines not covered because of pragma nocover + - set(dd_coverage_data["files"][path]["executed_lines"]) + ) + dd_coverage_missed_missing_lines = sorted( + set(coverage_py_data["files"][path]["missing_lines"]) + - set(dd_coverage_data["files"][path]["missing_lines"]) + ) + + coverage_py_missed_executed_lines = sorted( + set(dd_coverage_data["files"][path]["executed_lines"]) + - set(coverage_py_data["files"][path]["executed_lines"]) + ) + coverage_py_missed_missing_lines = sorted( + set(dd_coverage_data["files"][path]["missing_lines"]) + - set(coverage_py_data["files"][path]["missing_lines"]) + ) + + if dd_coverage_missed_executed_lines: + compared_data["dd_coverage_missed_executed_lines"][path] = collapse_ranges( + dd_coverage_missed_executed_lines + ) + if dd_coverage_missed_missing_lines: + compared_data["dd_coverage_missed_missing_lines"][path] = collapse_ranges(dd_coverage_missed_missing_lines) + if coverage_py_missed_executed_lines: + compared_data["coverage_py_missed_executed_lines"][path] = collapse_ranges( + coverage_py_missed_executed_lines + ) + if coverage_py_missed_missing_lines: + compared_data["coverage_py_missed_missing_lines"][path] = collapse_ranges(coverage_py_missed_missing_lines) + + return compared_data diff --git a/ddtrace/internal/coverage/util.py b/ddtrace/internal/coverage/util.py new file mode 100644 index 00000000000..e07e5ce22dd --- /dev/null +++ b/ddtrace/internal/coverage/util.py @@ -0,0 +1,20 @@ +import typing as t + + +def collapse_ranges(numbers: t.List[int]) -> t.List[t.Tuple[int, int]]: + # This function turns an ordered list of numbers into a list of ranges. + # For example, [1, 2, 3, 5, 6, 7, 9] becomes [(1, 3), (5, 7), (9, 9)] + if not numbers: + return [] + ranges = [] + start = end = numbers[0] + for number in numbers[1:]: + if number == end + 1: + end = number + else: + ranges.append((start, end)) + start = end = number + + ranges.append((start, end)) + + return ranges diff --git a/ddtrace/internal/module.py b/ddtrace/internal/module.py index 0d549a47c65..a8b861d3e57 100644 --- a/ddtrace/internal/module.py +++ b/ddtrace/internal/module.py @@ -6,6 +6,7 @@ from importlib.util import find_spec from pathlib import Path import sys +from types import CodeType from types import ModuleType import typing as t from weakref import WeakValueDictionary as wvdict @@ -15,6 +16,7 @@ ModuleHookType = t.Callable[[ModuleType], None] +TransformerType = t.Callable[[CodeType, ModuleType], CodeType] PreExecHookType = t.Callable[[t.Any, ModuleType], None] PreExecHookCond = t.Union[str, t.Callable[[str], bool]] @@ -23,20 +25,28 @@ _run_code = None +_run_module_transformers: t.List[TransformerType] = [] _post_run_module_hooks: t.List[ModuleHookType] = [] def _wrapped_run_code(*args: t.Any, **kwargs: t.Any) -> t.Dict[str, t.Any]: - global _run_code, _post_run_module_hooks - # DEV: If we are calling this wrapper then _run_code must have been set to # the original runpy._run_code. assert _run_code is not None - mod_name = get_argument_value(args, kwargs, 3, "mod_name") + code = t.cast(CodeType, get_argument_value(args, kwargs, 0, "code")) + mod_name = t.cast(str, get_argument_value(args, kwargs, 3, "mod_name")) + + module = sys.modules[mod_name] + + for transformer in _run_module_transformers: + code = transformer(code, module) + + kwargs.pop("code", None) + new_args = (code, *args[1:]) try: - return _run_code(*args, **kwargs) + return _run_code(*new_args, **kwargs) finally: module = sys.modules[mod_name] for hook in _post_run_module_hooks: @@ -129,6 +139,7 @@ def __init__(self, loader: t.Optional[Loader], spec: t.Optional[ModuleSpec] = No self.spec = spec self.callbacks: t.Dict[t.Any, t.Callable[[ModuleType], None]] = {} + self.transformers: t.Dict[t.Any, TransformerType] = {} # A missing loader is generally an indication of a namespace package. if loader is None or hasattr(loader, "create_module"): @@ -158,6 +169,9 @@ def namespace_module(self, spec: ModuleSpec) -> ModuleType: def add_callback(self, key: t.Any, callback: t.Callable[[ModuleType], None]) -> None: self.callbacks[key] = callback + def add_transformer(self, key: t.Any, transformer: TransformerType) -> None: + self.transformers[key] = transformer + def call_back(self, module: ModuleType) -> None: if module.__name__ == "pkg_resources": # DEV: pkg_resources support to prevent errors such as @@ -193,6 +207,19 @@ def _exec_module(self, module: ModuleType) -> None: # Collect and run only the first hook that matches the module. pre_exec_hook = None + _get_code = getattr(self.loader, "get_code", None) + if _get_code is not None: + + def get_code(_loader, fullname): + code = _get_code(fullname) + + for callback in self.transformers.values(): + code = callback(code, module) + + return code + + self.loader.get_code = get_code.__get__(self.loader, type(self.loader)) # type: ignore[union-attr] + for _ in sys.meta_path: if isinstance(_, ModuleWatchdog): try: @@ -261,6 +288,9 @@ def _remove_from_meta_path(cls) -> None: def after_import(self, module: ModuleType) -> None: raise NotImplementedError() + def transform(self, code: CodeType, _module: ModuleType) -> CodeType: + return code + def find_module(self, fullname: str, path: t.Optional[str] = None) -> t.Optional[Loader]: if fullname in self._finding: return None @@ -277,6 +307,7 @@ def find_module(self, fullname: str, path: t.Optional[str] = None) -> t.Optional ) loader.add_callback(type(self), self.after_import) + loader.add_transformer(type(self), self.transform) return t.cast(Loader, loader) @@ -309,6 +340,7 @@ def find_spec( spec.loader = t.cast(Loader, _ImportHookChainedLoader(loader, spec)) t.cast(_ImportHookChainedLoader, spec.loader).add_callback(type(self), self.after_import) + t.cast(_ImportHookChainedLoader, spec.loader).add_transformer(type(self), self.transform) return spec diff --git a/setup.py b/setup.py index 6daa456e5de..602cd5bad35 100644 --- a/setup.py +++ b/setup.py @@ -423,6 +423,13 @@ def get_exts_for(name): ], extra_compile_args=debug_compile_args, ), + Extension( + "ddtrace.internal.coverage._native", + sources=[ + "ddtrace/internal/coverage/_native.c", + ], + extra_compile_args=debug_compile_args, + ), ] if platform.system() not in ("Windows", ""): ext_modules.append( diff --git a/tests/.suitespec.json b/tests/.suitespec.json index 6ceccc20723..f0fde4fcde5 100644 --- a/tests/.suitespec.json +++ b/tests/.suitespec.json @@ -176,6 +176,9 @@ "coverage": [ "ddtrace/contrib/coverage/*" ], + "dd_coverage": [ + "ddtrace/internal/coverage/*" + ], "llmobs": [ "ddtrace/llmobs/*" ], @@ -618,6 +621,7 @@ "@ci_visibility", "@ci", "@coverage", + "@dd_coverage", "@git", "@pytest", "@codeowners", From beca788ced2aff737fc9b6f4c77b036b6c802568 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Wed, 22 May 2024 14:10:54 +0100 Subject: [PATCH 094/104] refactor: implement native periodic thread (#7659) This PR re-implements the periodic service to use a periodic thread implemented in C++. The reason for doing this is to reduce the dependency of the library on the `threading` module, which is the source of issues when it comes to frameworks such as `gevent`. This is the first step towards a refactoring that is aimed at removing the dependency on the `threading` module entirely, potentially leading to phasing out the module cloning mechanism, which is what we currently have in place to support frameworks such as the already mentioned `gevent`. ## Testing strategy The existing test suite provides already many reliable test cases that can give us good confidence about the proposed changes. Further testing is being performed in internal environments. ## Performance We do not expect any particular performance impact from this change ## Checklist - [x] Change(s) are motivated and described in the PR description. - [x] Testing strategy is described if automated tests are not included in the PR. - [x] Risk is outlined (performance impact, potential for breakage, maintainability, etc). - [x] Change is maintainable (easy to change, telemetry, documentation). - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed. If no release note is required, add label `changelog/no-changelog`. - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)). - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Title is accurate. - [x] No unnecessary changes are introduced. - [x] Description motivates each change. - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes unless absolutely necessary. - [x] Testing strategy adequately addresses listed risk(s). - [x] Change is maintainable (easy to change, telemetry, documentation). - [x] Release note makes sense to a user of the library. - [x] Reviewer has explicitly acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment. - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) - [x] If this PR touches code that signs or publishes builds or packages, or handles credentials of any kind, I've requested a review from `@DataDog/security-design-and-guidance`. - [x] This PR doesn't touch any of that. --------- Co-authored-by: Emmett Butler <723615+emmettbutler@users.noreply.github.com> Co-authored-by: Brett Langdon Co-authored-by: Emmett Butler --- ddtrace/internal/_threads.cpp | 528 ++++++++++++++++++ ddtrace/internal/_threads.pyi | 21 + ddtrace/internal/periodic.py | 121 +--- ddtrace/profiling/_threading.pyx | 8 +- ddtrace/profiling/collector/stack.pyx | 7 +- setup.py | 5 + tests/.suitespec.json | 1 + .../appsec/appsec/test_remoteconfiguration.py | 8 +- tests/conftest.py | 4 +- tests/internal/test_forksafe.py | 18 +- tests/{tracer => internal}/test_periodic.py | 17 +- tests/opentracer/test_tracer_gevent.py | 3 +- tests/profiling/collector/test_stack.py | 8 - tests/subprocesstest.py | 3 +- 14 files changed, 612 insertions(+), 140 deletions(-) create mode 100644 ddtrace/internal/_threads.cpp create mode 100644 ddtrace/internal/_threads.pyi rename tests/{tracer => internal}/test_periodic.py (88%) diff --git a/ddtrace/internal/_threads.cpp b/ddtrace/internal/_threads.cpp new file mode 100644 index 00000000000..3e97dd82003 --- /dev/null +++ b/ddtrace/internal/_threads.cpp @@ -0,0 +1,528 @@ +#define PY_SSIZE_T_CLEAN +#include + +#include "structmember.h" + +#include + +#include +#include +#include +#include +#include + +// ---------------------------------------------------------------------------- +/** + * Ensure that the GIL is held. + */ +class GILGuard +{ + public: + inline GILGuard() + { + if (!_Py_IsFinalizing()) + _state = PyGILState_Ensure(); + } + inline ~GILGuard() + { + if (PyGILState_Check()) + PyGILState_Release(_state); + } + + private: + PyGILState_STATE _state; +}; + +// ---------------------------------------------------------------------------- +/** + * Release the GIL to allow other threads to run. + */ +class AllowThreads +{ + public: + inline AllowThreads() + { + if (!_Py_IsFinalizing()) + _state = PyEval_SaveThread(); + } + inline ~AllowThreads() + { + if (!_Py_IsFinalizing()) + PyEval_RestoreThread(_state); + } + + private: + PyThreadState* _state; +}; + +// ---------------------------------------------------------------------------- +class PyRef +{ + public: + inline PyRef(PyObject* obj) + : _obj(obj) + { + Py_INCREF(_obj); + } + inline ~PyRef() { Py_DECREF(_obj); } + + private: + PyObject* _obj; +}; + +// ---------------------------------------------------------------------------- +class Event +{ + public: + void set() + { + std::lock_guard lock(_mutex); + _set = true; + _cond.notify_all(); + } + + void wait() + { + std::unique_lock lock(_mutex); + _cond.wait(lock, [this]() { return _set; }); + } + + bool wait(std::chrono::milliseconds timeout) + { + std::unique_lock lock(_mutex); + return _cond.wait_for(lock, timeout, [this]() { return _set; }); + } + + void clear() + { + std::lock_guard lock(_mutex); + _set = false; + } + + private: + std::condition_variable _cond; + std::mutex _mutex; + bool _set = false; +}; + +// ---------------------------------------------------------------------------- +typedef struct periodic_thread +{ + PyObject_HEAD + + double interval; + PyObject* name; + PyObject* ident; + + PyObject* _target; + PyObject* _on_shutdown; + + PyObject* _ddtrace_profiling_ignore; + + bool _stopping; + bool _atexit; + bool _after_fork; + + std::unique_ptr _started; + std::unique_ptr _stopped; + std::unique_ptr _request; + std::unique_ptr _served; + + std::unique_ptr _awake_mutex; + + std::unique_ptr _thread; +} PeriodicThread; + +// ---------------------------------------------------------------------------- +// Maintain a mapping of thread ID to PeriodicThread objects. This is similar +// to threading._active. +static PyObject* _periodic_threads = NULL; + +// ---------------------------------------------------------------------------- +static PyMemberDef PeriodicThread_members[] = { + { "interval", T_DOUBLE, offsetof(PeriodicThread, interval), 0, "thread interval" }, + + { "name", T_OBJECT_EX, offsetof(PeriodicThread, name), 0, "thread name" }, + { "ident", T_OBJECT_EX, offsetof(PeriodicThread, ident), 0, "thread ID" }, + + { "_ddtrace_profiling_ignore", + T_OBJECT_EX, + offsetof(PeriodicThread, _ddtrace_profiling_ignore), + 0, + "whether to ignore the thread for profiling" }, + + { NULL } /* Sentinel */ +}; + +// ---------------------------------------------------------------------------- +static int +PeriodicThread_init(PeriodicThread* self, PyObject* args, PyObject* kwargs) +{ + static const char* kwlist[] = { "interval", "target", "name", "on_shutdown", NULL }; + + self->name = Py_None; + self->_on_shutdown = Py_None; + + if (!PyArg_ParseTupleAndKeywords( + args, kwargs, "dO|OO", (char**)kwlist, &self->interval, &self->_target, &self->name, &self->_on_shutdown)) + return -1; + + Py_INCREF(self->_target); + Py_INCREF(self->name); + Py_INCREF(self->_on_shutdown); + + Py_INCREF(Py_None); + self->ident = Py_None; + + Py_INCREF(Py_True); + self->_ddtrace_profiling_ignore = Py_True; + + self->_stopping = false; + self->_atexit = false; + self->_after_fork = false; + + self->_started = std::make_unique(); + self->_stopped = std::make_unique(); + self->_request = std::make_unique(); + self->_served = std::make_unique(); + + self->_awake_mutex = std::make_unique(); + + return 0; +} + +// ---------------------------------------------------------------------------- +static inline bool +PeriodicThread__periodic(PeriodicThread* self) +{ + PyObject* result = PyObject_CallObject(self->_target, NULL); + + if (result == NULL) { + PyErr_Print(); + } + + Py_XDECREF(result); + + return result == NULL; +} + +// ---------------------------------------------------------------------------- +static inline void +PeriodicThread__on_shutdown(PeriodicThread* self) +{ + PyObject* result = PyObject_CallObject(self->_on_shutdown, NULL); + + if (result == NULL) { + PyErr_Print(); + } + + Py_XDECREF(result); +} + +// ---------------------------------------------------------------------------- +static PyObject* +PeriodicThread_start(PeriodicThread* self, PyObject* args) +{ + if (self->_thread != nullptr) { + PyErr_SetString(PyExc_RuntimeError, "Thread already started"); + return NULL; + } + + if (self->_stopping) + Py_RETURN_NONE; + + // Start the thread + self->_thread = std::make_unique([self]() { + GILGuard _gil; + + PyRef _((PyObject*)self); + + // Retrieve the thread ID + { + Py_DECREF(self->ident); + self->ident = PyLong_FromLong((long)PyThreadState_Get()->thread_id); + + // Map the PeriodicThread object to its thread ID + PyDict_SetItem(_periodic_threads, self->ident, (PyObject*)self); + } + + // Mark the thread as started from this point. + self->_started->set(); + + bool error = false; + auto interval = std::chrono::milliseconds((long long)(self->interval * 1000)); + + while (!self->_stopping) { + { + AllowThreads _; + + if (self->_request->wait(interval)) { + if (self->_stopping) + break; + + // Awake signal + self->_request->clear(); + self->_served->set(); + } + } + + if (_Py_IsFinalizing()) + break; + + if (PeriodicThread__periodic(self)) { + // Error + error = true; + break; + } + } + + // Run the shutdown callback if there was no error and we are not + // at Python shutdown. + if (!self->_atexit && !error && self->_on_shutdown != Py_None && !_Py_IsFinalizing()) + PeriodicThread__on_shutdown(self); + + // Notify the join method that the thread has stopped + self->_stopped->set(); + }); + + // Detach the thread. We will make our own joinable mechanism. + self->_thread->detach(); + + // Wait for the thread to start + { + AllowThreads _; + + self->_started->wait(); + } + + Py_RETURN_NONE; +} + +// ---------------------------------------------------------------------------- +static PyObject* +PeriodicThread_awake(PeriodicThread* self, PyObject* args) +{ + if (self->_thread == nullptr) { + PyErr_SetString(PyExc_RuntimeError, "Thread not started"); + return NULL; + } + + { + AllowThreads _; + std::lock_guard lock(*self->_awake_mutex); + + self->_served->clear(); + self->_request->set(); + self->_served->wait(); + } + + Py_RETURN_NONE; +} + +// ---------------------------------------------------------------------------- +static PyObject* +PeriodicThread_stop(PeriodicThread* self, PyObject* args) +{ + if (self->_thread == nullptr) { + PyErr_SetString(PyExc_RuntimeError, "Thread not started"); + return NULL; + } + + self->_stopping = true; + self->_request->set(); + + Py_RETURN_NONE; +} + +// ---------------------------------------------------------------------------- +static PyObject* +PeriodicThread_join(PeriodicThread* self, PyObject* args, PyObject* kwargs) +{ + if (self->_thread == nullptr) { + PyErr_SetString(PyExc_RuntimeError, "Periodic thread not started"); + return NULL; + } + + if (self->_thread->get_id() == std::this_thread::get_id()) { + PyErr_SetString(PyExc_RuntimeError, "Cannot join the current periodic thread"); + return NULL; + } + + if (self->_after_fork) { + // The thread is no longer running so it makes no sense to join it. + Py_RETURN_NONE; + } + + PyObject* timeout = Py_None; + + if (args != NULL && kwargs != NULL) { + static const char* argnames[] = { "timeout", NULL }; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|O", (char**)argnames, &timeout)) + return NULL; + } + + if (timeout == Py_None) { + AllowThreads _; + + self->_stopped->wait(); + } else { + double timeout_value = 0.0; + + if (PyFloat_Check(timeout)) { + timeout_value = PyFloat_AsDouble(timeout); + } else if (PyLong_Check(timeout)) { + timeout_value = PyLong_AsDouble(timeout); + } else { + PyErr_SetString(PyExc_TypeError, "timeout must be a float or an int"); + return NULL; + } + + AllowThreads _; + + auto interval = std::chrono::milliseconds((long long)(timeout_value * 1000)); + + self->_stopped->wait(interval); + } + + Py_RETURN_NONE; +} + +// ---------------------------------------------------------------------------- +static PyObject* +PeriodicThread__atexit(PeriodicThread* self, PyObject* args) +{ + self->_atexit = true; + + if (PeriodicThread_stop(self, NULL) == NULL) + return NULL; + + if (PeriodicThread_join(self, NULL, NULL) == NULL) + return NULL; + + Py_RETURN_NONE; +} + +// ---------------------------------------------------------------------------- +static PyObject* +PeriodicThread__after_fork(PeriodicThread* self, PyObject* args) +{ + self->_after_fork = true; + + Py_RETURN_NONE; +} + +// ---------------------------------------------------------------------------- +static void +PeriodicThread_dealloc(PeriodicThread* self) +{ + // Since the native thread holds a strong reference to this object, we + // can only get here if the thread has actually stopped. + + if (_Py_IsFinalizing()) + // Do nothing. We are about to terminate and release resources anyway. + return; + + // If we are trying to stop from the same thread, then we are still running. + // This should happen rarely, so we don't worry about the memory leak this + // will cause. + if (self->_thread != NULL && self->_thread->get_id() == std::this_thread::get_id()) + return; + + // Unmap the PeriodicThread + if (self->ident != NULL && PyDict_Contains(_periodic_threads, self->ident)) + PyDict_DelItem(_periodic_threads, self->ident); + + Py_XDECREF(self->name); + Py_XDECREF(self->_target); + Py_XDECREF(self->_on_shutdown); + + Py_XDECREF(self->ident); + Py_XDECREF(self->_ddtrace_profiling_ignore); + + self->_thread = nullptr; + + self->_started = nullptr; + self->_stopped = nullptr; + self->_request = nullptr; + self->_served = nullptr; + + self->_awake_mutex = nullptr; + + Py_TYPE(self)->tp_free((PyObject*)self); +} + +// ---------------------------------------------------------------------------- +static PyMethodDef PeriodicThread_methods[] = { + { "start", (PyCFunction)PeriodicThread_start, METH_NOARGS, "Start the thread" }, + { "awake", (PyCFunction)PeriodicThread_awake, METH_NOARGS, "Awake the thread" }, + { "stop", (PyCFunction)PeriodicThread_stop, METH_NOARGS, "Stop the thread" }, + { "join", (PyCFunction)PeriodicThread_join, METH_VARARGS | METH_KEYWORDS, "Join the thread" }, + /* Private */ + { "_atexit", (PyCFunction)PeriodicThread__atexit, METH_NOARGS, "Stop the thread at exit" }, + { "_after_fork", (PyCFunction)PeriodicThread__after_fork, METH_NOARGS, "Mark the thread as after fork" }, + { NULL } /* Sentinel */ +}; + +// ---------------------------------------------------------------------------- +static PyTypeObject PeriodicThreadType = { + .ob_base = PyVarObject_HEAD_INIT(NULL, 0).tp_name = "ddtrace.internal._threads.PeriodicThread", + .tp_basicsize = sizeof(PeriodicThread), + .tp_itemsize = 0, + .tp_dealloc = (destructor)PeriodicThread_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_doc = PyDoc_STR("Native thread calling a Python function periodically"), + .tp_methods = PeriodicThread_methods, + .tp_members = PeriodicThread_members, + .tp_init = (initproc)PeriodicThread_init, + .tp_new = PyType_GenericNew, +}; + +// ---------------------------------------------------------------------------- +static PyMethodDef _threads_methods[] = { + { NULL, NULL, 0, NULL } /* Sentinel */ +}; + +// ---------------------------------------------------------------------------- +static struct PyModuleDef threadsmodule = { + PyModuleDef_HEAD_INIT, + "_threads", /* name of module */ + NULL, /* module documentation, may be NULL */ + -1, /* size of per-interpreter state of the module, + or -1 if the module keeps state in global variables. */ + _threads_methods, +}; + +// ---------------------------------------------------------------------------- +PyMODINIT_FUNC +PyInit__threads(void) +{ + PyObject* m = NULL; + + if (PyType_Ready(&PeriodicThreadType) < 0) + return NULL; + + _periodic_threads = PyDict_New(); + if (_periodic_threads == NULL) + return NULL; + + m = PyModule_Create(&threadsmodule); + if (m == NULL) + goto error; + + Py_INCREF(&PeriodicThreadType); + if (PyModule_AddObject(m, "PeriodicThread", (PyObject*)&PeriodicThreadType) < 0) { + Py_DECREF(&PeriodicThreadType); + goto error; + } + + if (PyModule_AddObject(m, "periodic_threads", _periodic_threads) < 0) + goto error; + + return m; + +error: + Py_XDECREF(_periodic_threads); + Py_XDECREF(m); + + return NULL; +} diff --git a/ddtrace/internal/_threads.pyi b/ddtrace/internal/_threads.pyi new file mode 100644 index 00000000000..f3adb3ae9d6 --- /dev/null +++ b/ddtrace/internal/_threads.pyi @@ -0,0 +1,21 @@ +import typing as t + +class PeriodicThread: + name: str + ident: int + + def __init__( + self, + interval: float, + target: t.Callable, + name: t.Optional[str] = None, + on_shutdown: t.Optional[t.Callable] = None, + ) -> None: ... + def start(self) -> None: ... + def stop(self) -> None: ... + def join(self, timeout: t.Optional[float] = None) -> None: ... + def awake(self) -> None: ... + def _atexit(self) -> None: ... + def _after_fork(self) -> None: ... + +periodic_threads: t.Dict[int, PeriodicThread] diff --git a/ddtrace/internal/periodic.py b/ddtrace/internal/periodic.py index e8880252345..684248d7b11 100644 --- a/ddtrace/internal/periodic.py +++ b/ddtrace/internal/periodic.py @@ -1,108 +1,33 @@ # -*- encoding: utf-8 -*- -import threading +import atexit import typing # noqa:F401 import attr +from ddtrace.internal import forksafe from ddtrace.internal import service +from ddtrace.internal._threads import PeriodicThread +from ddtrace.internal._threads import periodic_threads -from . import forksafe +@atexit.register +def _(): + # If the interpreter is shutting down we need to make sure that the threads + # are stopped before the runtime is marked as finalising. This is because + # any attempt to acquire the GIL while the runtime is finalising will cause + # the acquiring thread to be terminated with pthread_exit (on Linux). This + # causes a SIGABRT with GCC that cannot be caught, so we need to avoid + # getting to that stage. + for thread in periodic_threads.values(): + thread._atexit() -class PeriodicThread(threading.Thread): - """Periodic thread. - This class can be used to instantiate a worker thread that will run its `run_periodic` function every `interval` - seconds. - - """ - - _ddtrace_profiling_ignore = True - - def __init__( - self, - interval, # type: float - target, # type: typing.Callable[[], typing.Any] - name=None, # type: typing.Optional[str] - on_shutdown=None, # type: typing.Optional[typing.Callable[[], typing.Any]] - ): - # type: (...) -> None - """Create a periodic thread. - - :param interval: The interval in seconds to wait between execution of the periodic function. - :param target: The periodic function to execute every interval. - :param name: The name of the thread. - :param on_shutdown: The function to call when the thread shuts down. - """ - super(PeriodicThread, self).__init__(name=name) - self._target = target - self._on_shutdown = on_shutdown - self.interval = interval - self.quit = forksafe.Event() - self.daemon = True - - def stop(self): - """Stop the thread.""" - # NOTE: make sure the thread is alive before using self.quit: - # 1. self.quit is Lock-based - # 2. if we're a child trying to stop a Thread, - # the Lock might have been locked in a parent process while forking so that'd block forever - if self.is_alive(): - self.quit.set() - - def run(self): - """Run the target function periodically.""" - while not self.quit.wait(self.interval): - self._target() - if self._on_shutdown is not None: - self._on_shutdown() - - -class AwakeablePeriodicThread(PeriodicThread): - """Periodic thread that can be awakened on demand. - - This class can be used to instantiate a worker thread that will run its - `run_periodic` function every `interval` seconds, or upon request. - """ - - def __init__( - self, - interval, # type: float - target, # type: typing.Callable[[], typing.Any] - name=None, # type: typing.Optional[str] - on_shutdown=None, # type: typing.Optional[typing.Callable[[], typing.Any]] - ): - # type: (...) -> None - """Create a periodic thread that can be awakened on demand.""" - super(AwakeablePeriodicThread, self).__init__(interval, target, name, on_shutdown) - self.request = forksafe.Event() - self.served = forksafe.Event() - self.awake_lock = forksafe.Lock() - - def awake(self): - """Awake the thread.""" - with self.awake_lock: - self.served.clear() - self.request.set() - self.served.wait() - - def stop(self): - super().stop() - self.request.set() - - def run(self): - """Run the target function periodically or on demand.""" - while not self.quit.is_set(): - self._target() - - if self.request.wait(self.interval): - if self.quit.is_set(): - break - self.request.clear() - self.served.set() - - if self._on_shutdown is not None: - self._on_shutdown() +@forksafe.register +def _(): + # No threads are running after a fork so we clean up the periodic threads + for thread in periodic_threads.values(): + thread._after_fork() + periodic_threads.clear() @attr.s(eq=False) @@ -112,8 +37,6 @@ class PeriodicService(service.Service): _interval = attr.ib(type=float) _worker = attr.ib(default=None, init=False, repr=False) - __thread_class__ = PeriodicThread - @property def interval(self): # type: (...) -> float @@ -133,7 +56,7 @@ def interval( def _start_service(self, *args, **kwargs): # type: (typing.Any, typing.Any) -> None """Start the periodic service.""" - self._worker = self.__thread_class__( + self._worker = PeriodicThread( self.interval, target=self.periodic, name="%s:%s" % (self.__class__.__module__, self.__class__.__name__), @@ -167,8 +90,6 @@ def periodic(self): class AwakeablePeriodicService(PeriodicService): """A service that runs periodically but that can also be awakened on demand.""" - __thread_class__ = AwakeablePeriodicThread - def awake(self): # type: (...) -> None self._worker.awake() diff --git a/ddtrace/profiling/_threading.pyx b/ddtrace/profiling/_threading.pyx index 93a121a3ce2..337eaa48fb3 100644 --- a/ddtrace/profiling/_threading.pyx +++ b/ddtrace/profiling/_threading.pyx @@ -7,6 +7,7 @@ import weakref import attr from six.moves import _thread +from ddtrace.internal._threads import periodic_threads from ddtrace.internal._unpatched import _threading as ddtrace_threading @@ -70,8 +71,11 @@ cpdef get_thread_by_id(thread_id): cpdef get_thread_name(thread_id): - thread = get_thread_by_id(thread_id) - return thread.name if thread is not None else None + try: + return periodic_threads[thread_id].name + except KeyError: + thread = get_thread_by_id(thread_id) + return thread.name if thread is not None else None cpdef get_thread_native_id(thread_id): diff --git a/ddtrace/profiling/collector/stack.pyx b/ddtrace/profiling/collector/stack.pyx index b49720d55a4..3ba4e8af103 100644 --- a/ddtrace/profiling/collector/stack.pyx +++ b/ddtrace/profiling/collector/stack.pyx @@ -1,6 +1,7 @@ """CPU profiling collector.""" from __future__ import absolute_import +from itertools import chain import logging import sys import typing @@ -12,6 +13,7 @@ from ddtrace.internal._unpatched import _threading as ddtrace_threading from ddtrace._trace import context from ddtrace._trace import span as ddspan from ddtrace.internal import compat +from ddtrace.internal._threads import periodic_threads from ddtrace.internal.datadog.profiling import ddup from ddtrace.internal.datadog.profiling import stack_v2 from ddtrace.internal.utils import formats @@ -290,9 +292,12 @@ cdef collect_threads(thread_id_ignore_list, thread_time, thread_span_links) with cdef stack_collect(ignore_profiler, thread_time, max_nframes, interval, wall_time, thread_span_links, collect_endpoint): # Do not use `threading.enumerate` to not mess with locking (gevent!) + # Also collect the native threads, that are not registered with the built-in + # threading module, to keep backward compatibility with the previous + # pure-Python implementation of periodic threads. thread_id_ignore_list = { thread_id - for thread_id, thread in ddtrace_threading._active.items() + for thread_id, thread in chain(periodic_threads.items(), ddtrace_threading._active.items()) if getattr(thread, "_ddtrace_profiling_ignore", False) } if ignore_profiler else set() diff --git a/setup.py b/setup.py index 602cd5bad35..b43b85ecb79 100644 --- a/setup.py +++ b/setup.py @@ -423,6 +423,11 @@ def get_exts_for(name): ], extra_compile_args=debug_compile_args, ), + Extension( + "ddtrace.internal._threads", + sources=["ddtrace/internal/_threads.cpp"], + extra_compile_args=["-std=c++17", "-Wall", "-Wextra"] if CURRENT_OS != "Windows" else ["/std:c++20"], + ), Extension( "ddtrace.internal.coverage._native", sources=[ diff --git a/tests/.suitespec.json b/tests/.suitespec.json index f0fde4fcde5..2637c4d9b3b 100644 --- a/tests/.suitespec.json +++ b/tests/.suitespec.json @@ -38,6 +38,7 @@ "ddtrace/internal/_rand.pyi", "ddtrace/internal/_rand.pyx", "ddtrace/internal/_stdint.h", + "ddtrace/internal/_threads.*", "ddtrace/internal/_unpatched.py", "ddtrace/internal/agent.py", "ddtrace/internal/assembly.py", diff --git a/tests/appsec/appsec/test_remoteconfiguration.py b/tests/appsec/appsec/test_remoteconfiguration.py index 503f3b03b83..03315ebbb0a 100644 --- a/tests/appsec/appsec/test_remoteconfiguration.py +++ b/tests/appsec/appsec/test_remoteconfiguration.py @@ -28,6 +28,7 @@ from ddtrace.internal.remoteconfig.client import ConfigMetadata from ddtrace.internal.remoteconfig.client import TargetFile from ddtrace.internal.remoteconfig.worker import remoteconfig_poller +from ddtrace.internal.service import ServiceStatus from ddtrace.internal.utils.formats import asbool import tests.appsec.rules as rules from tests.appsec.utils import Either @@ -899,8 +900,7 @@ def test_rc_activation_ip_blocking_data(tracer, remote_config_worker): ] } } - # flaky test - # assert not remoteconfig_poller._worker + assert remoteconfig_poller.status == ServiceStatus.STOPPED _appsec_callback(rc_config, tracer) with _asm_request_context.asm_request_context_manager("8.8.4.4", {}): @@ -930,7 +930,7 @@ def test_rc_activation_ip_blocking_data_expired(tracer, remote_config_worker): } } - assert not remoteconfig_poller._worker + assert remoteconfig_poller.status == ServiceStatus.STOPPED _appsec_callback(rc_config, tracer) @@ -960,7 +960,7 @@ def test_rc_activation_ip_blocking_data_not_expired(tracer, remote_config_worker } } - assert not remoteconfig_poller._worker + assert remoteconfig_poller.status == ServiceStatus.STOPPED _appsec_callback(rc_config, tracer) diff --git a/tests/conftest.py b/tests/conftest.py index 94ba9e5e2e2..af74a2c355d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -396,8 +396,8 @@ def git_repo(git_repo_empty): def _stop_remote_config_worker(): - if remoteconfig_poller._worker: - remoteconfig_poller._stop_service(True) + if remoteconfig_poller.status == ServiceStatus.RUNNING: + remoteconfig_poller.stop(join=True) remoteconfig_poller._worker = None diff --git a/tests/internal/test_forksafe.py b/tests/internal/test_forksafe.py index 111ca8ee764..e9c5a42c9ef 100644 --- a/tests/internal/test_forksafe.py +++ b/tests/internal/test_forksafe.py @@ -309,6 +309,7 @@ def test_gevent_gunicorn_behaviour(): # to avoid problems with threads/forks that we saw previously # when running gunicorn with gevent workers + import os import sys assert "gevent" not in sys.modules @@ -323,15 +324,18 @@ def test_gevent_gunicorn_behaviour(): class TestService(PeriodicService): def __init__(self): - super(TestService, self).__init__(interval=1.0) + super(TestService, self).__init__(interval=0.1) + self._has_run = False def periodic(self): - sys.stdout.write("T") - self.stop() + if not self._has_run: + sys.stdout.write("T") + sys.stdout.flush() + self._has_run = True service = TestService() service.start() - atexit.register(service.stop) + atexit.register(lambda: service.stop() and service.join(1)) def restart_service(): global service @@ -340,11 +344,9 @@ def restart_service(): service.start() forksafe.register(restart_service) - atexit.register(lambda: service.join(1)) # ---- Application code ---- - import os # noqa:F401 import sys # noqa:F401 import gevent.hub # noqa:F401 @@ -357,7 +359,7 @@ def run_child(): sys.stdout.write("C") - gevent.sleep(1.5) + gevent.sleep(1) def fork_workers(num): for _ in range(num): @@ -367,4 +369,6 @@ def fork_workers(num): fork_workers(3) + gevent.sleep(1) + exit() diff --git a/tests/tracer/test_periodic.py b/tests/internal/test_periodic.py similarity index 88% rename from tests/tracer/test_periodic.py rename to tests/internal/test_periodic.py index 5b14f5e5c1e..7c853d64786 100644 --- a/tests/tracer/test_periodic.py +++ b/tests/internal/test_periodic.py @@ -1,4 +1,3 @@ -import threading from threading import Event from time import sleep @@ -26,14 +25,10 @@ def _on_shutdown(): t.start() thread_started.wait() thread_continue.set() - assert t.is_alive() t.stop() t.join() - assert not t.is_alive() assert x["OK"] assert x["DOWN"] - if hasattr(threading, "get_native_id"): - assert t.native_id is not None def test_periodic_double_start(): @@ -44,6 +39,8 @@ def _run_periodic(): t.start() with pytest.raises(RuntimeError): t.start() + t.stop() + t.join() def test_periodic_error(): @@ -98,14 +95,6 @@ def test_periodic_join_stop_no_start(): t.stop() -def test_is_alive_before_start(): - def x(): - pass - - t = periodic.PeriodicThread(1, x) - assert not t.is_alive() - - def test_awakeable_periodic_service(): queue = [] @@ -129,4 +118,4 @@ def periodic(self): awake_me.stop() - assert queue == list(range(n + 2)) + assert queue == list(range(n + 1)) diff --git a/tests/opentracer/test_tracer_gevent.py b/tests/opentracer/test_tracer_gevent.py index afacc69295a..59a52faaa0e 100644 --- a/tests/opentracer/test_tracer_gevent.py +++ b/tests/opentracer/test_tracer_gevent.py @@ -116,6 +116,7 @@ def test_trace_spawn_multiple_greenlets_multiple_traces_ot_parent(self, ot_trace Uses an opentracer span as the parent span. """ + # multiple greenlets must be part of the same trace def entrypoint(): with ot_tracer.start_active_span("greenlet.main"): @@ -157,6 +158,7 @@ def test_trace_spawn_multiple_greenlets_multiple_traces_dd_parent(self, ot_trace Uses an opentracer span as the parent span. """ + # multiple greenlets must be part of the same trace def entrypoint(): with dd_tracer.trace("greenlet.main"): @@ -190,4 +192,3 @@ def green_2(): assert worker_2.name == "greenlet.worker" assert worker_2.resource == "greenlet.worker" assert worker_2.parent_id == parent_span.span_id - diff --git a/tests/profiling/collector/test_stack.py b/tests/profiling/collector/test_stack.py index 2e6e577b912..fd5e13ad2c8 100644 --- a/tests/profiling/collector/test_stack.py +++ b/tests/profiling/collector/test_stack.py @@ -248,14 +248,6 @@ def test_ignore_profiler_single(): assert thread_id not in {e.thread_id for e in events} -def test_no_ignore_profiler_single(): - r, c, thread_id = test_collector._test_collector_collect( - stack.StackCollector, stack_event.StackSampleEvent, ignore_profiler=False - ) - events = r.events[stack_event.StackSampleEvent] - assert thread_id in {e.thread_id for e in events} - - @pytest.mark.skipif(not TESTING_GEVENT, reason="Not testing gevent") @pytest.mark.subprocess(ddtrace_run=True) def test_ignore_profiler_gevent_task(): diff --git a/tests/subprocesstest.py b/tests/subprocesstest.py index e09ec0e4bd6..c7cd1c39c36 100644 --- a/tests/subprocesstest.py +++ b/tests/subprocesstest.py @@ -126,7 +126,8 @@ def _run_test_in_subprocess(self, result): if sp.returncode and "_pytest.outcomes.xfailed" not in stderr.lower(): try: cmdf = " ".join(sp_test_cmd) - raise Exception('Subprocess Test "{}" Failed'.format(cmdf)) + msg = f'Subprocess Test "{cmdf}" Failed (exit code {sp.returncode}):\n{stderr}' + raise Exception(msg) except Exception: exc_info = sys.exc_info() From 8c2758ad21cbbf24ab5cc3db351cb2b7cad0ff44 Mon Sep 17 00:00:00 2001 From: lievan <42917263+lievan@users.noreply.github.com> Date: Wed, 22 May 2024 10:33:27 -0400 Subject: [PATCH 095/104] feat(llmobs): improve langchain llmobs span names (#9333) Change the span name for langchain auto-instrumented spans to be module + class APM span name doesn't change -- just the name on the llmobs span event we send to evp ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: lievan --- ddtrace/llmobs/_constants.py | 2 ++ ddtrace/llmobs/_trace_processor.py | 3 ++- ddtrace/llmobs/_utils.py | 7 +++++++ tests/contrib/langchain/test_langchain.py | 2 ++ tests/contrib/langchain/test_langchain_community.py | 2 ++ tests/llmobs/_utils.py | 13 ++++++++++--- tests/llmobs/test_llmobs_trace_processor.py | 12 ++++++++++++ 7 files changed, 37 insertions(+), 4 deletions(-) diff --git a/ddtrace/llmobs/_constants.py b/ddtrace/llmobs/_constants.py index 990ab2977f2..ec08a655cae 100644 --- a/ddtrace/llmobs/_constants.py +++ b/ddtrace/llmobs/_constants.py @@ -20,3 +20,5 @@ SPAN_START_WHILE_DISABLED_WARNING = ( "Span started while LLMObs is disabled." " Spans will not be sent to LLM Observability." ) + +LANGCHAIN_APM_SPAN_NAME = "langchain.request" diff --git a/ddtrace/llmobs/_trace_processor.py b/ddtrace/llmobs/_trace_processor.py index 815ada53461..ef57c6aeedf 100644 --- a/ddtrace/llmobs/_trace_processor.py +++ b/ddtrace/llmobs/_trace_processor.py @@ -33,6 +33,7 @@ from ddtrace.llmobs._utils import _get_llmobs_parent_id from ddtrace.llmobs._utils import _get_ml_app from ddtrace.llmobs._utils import _get_session_id +from ddtrace.llmobs._utils import _get_span_name log = get_logger(__name__) @@ -105,7 +106,7 @@ def _llmobs_span_event(self, span: Span) -> Dict[str, Any]: "span_id": str(span.span_id), "parent_id": str(_get_llmobs_parent_id(span) or "undefined"), "session_id": session_id, - "name": span.name, + "name": _get_span_name(span), "tags": self._llmobs_tags(span, ml_app=ml_app, session_id=session_id), "start_ns": span.start_ns, "duration": span.duration_ns, diff --git a/ddtrace/llmobs/_utils.py b/ddtrace/llmobs/_utils.py index f557efdb1dc..2e728168306 100644 --- a/ddtrace/llmobs/_utils.py +++ b/ddtrace/llmobs/_utils.py @@ -3,6 +3,7 @@ from ddtrace import Span from ddtrace import config from ddtrace.ext import SpanTypes +from ddtrace.llmobs._constants import LANGCHAIN_APM_SPAN_NAME from ddtrace.llmobs._constants import ML_APP from ddtrace.llmobs._constants import SESSION_ID @@ -27,6 +28,12 @@ def _get_llmobs_parent_id(span: Span) -> Optional[int]: return None +def _get_span_name(span: Span) -> str: + if span.name == LANGCHAIN_APM_SPAN_NAME and span.resource != "": + return span.resource + return span.name + + def _get_ml_app(span: Span) -> str: """ Return the ML app name for a given span, by checking the span's nearest LLMObs span ancestor. diff --git a/tests/contrib/langchain/test_langchain.py b/tests/contrib/langchain/test_langchain.py index 43d1e6153fd..156a1bf79da 100644 --- a/tests/contrib/langchain/test_langchain.py +++ b/tests/contrib/langchain/test_langchain.py @@ -1293,6 +1293,7 @@ def _expected_llmobs_chain_call(span, metadata=None, input_value=None, output_va tags={ "ml_app": "langchain_test", }, + integration="langchain", ) @staticmethod @@ -1334,6 +1335,7 @@ def _expected_llmobs_llm_call(span, provider="openai", input_roles=[None], outpu tags={ "ml_app": "langchain_test", }, + integration="langchain", ) @classmethod diff --git a/tests/contrib/langchain/test_langchain_community.py b/tests/contrib/langchain/test_langchain_community.py index 929e53d6e68..35f56de208b 100644 --- a/tests/contrib/langchain/test_langchain_community.py +++ b/tests/contrib/langchain/test_langchain_community.py @@ -1281,6 +1281,7 @@ def _expected_llmobs_chain_call(span, input_parameters=None, input_value=None, o tags={ "ml_app": "langchain_community_test", }, + integration="langchain", ) @staticmethod @@ -1322,6 +1323,7 @@ def _expected_llmobs_llm_call(span, provider="openai", input_roles=[None], outpu tags={ "ml_app": "langchain_community_test", }, + integration="langchain", ) @classmethod diff --git a/tests/llmobs/_utils.py b/tests/llmobs/_utils.py index 2cb1456ccc5..6a56804dcf7 100644 --- a/tests/llmobs/_utils.py +++ b/tests/llmobs/_utils.py @@ -56,6 +56,7 @@ def _expected_llmobs_llm_span_event( error=None, error_message=None, error_stack=None, + integration=None, ): """ Helper function to create an expected LLM span event. @@ -73,7 +74,9 @@ def _expected_llmobs_llm_span_event( error_message: error message error_stack: error stack """ - span_event = _llmobs_base_span_event(span, span_kind, tags, session_id, error, error_message, error_stack) + span_event = _llmobs_base_span_event( + span, span_kind, tags, session_id, error, error_message, error_stack, integration=integration + ) meta_dict = {"input": {}, "output": {}} if input_messages is not None: meta_dict["input"].update({"messages": input_messages}) @@ -110,6 +113,7 @@ def _expected_llmobs_non_llm_span_event( error=None, error_message=None, error_stack=None, + integration=None, ): """ Helper function to create an expected span event of type (workflow, task, tool). @@ -125,7 +129,9 @@ def _expected_llmobs_non_llm_span_event( error_message: error message error_stack: error stack """ - span_event = _llmobs_base_span_event(span, span_kind, tags, session_id, error, error_message, error_stack) + span_event = _llmobs_base_span_event( + span, span_kind, tags, session_id, error, error_message, error_stack, integration=integration + ) meta_dict = {"input": {}, "output": {}} if input_value is not None: meta_dict["input"].update({"value": input_value}) @@ -153,13 +159,14 @@ def _llmobs_base_span_event( error=None, error_message=None, error_stack=None, + integration=None, ): span_event = { "span_id": str(span.span_id), "trace_id": "{:x}".format(span.trace_id), "parent_id": _get_llmobs_parent_id(span), "session_id": session_id or "{:x}".format(span.trace_id), - "name": span.name, + "name": span.resource if integration == "langchain" else span.name, "tags": _expected_llmobs_tags(span, tags=tags, error=error, session_id=session_id), "start_ns": span.start_ns, "duration": span.duration_ns, diff --git a/tests/llmobs/test_llmobs_trace_processor.py b/tests/llmobs/test_llmobs_trace_processor.py index 828dc547219..3191da41bad 100644 --- a/tests/llmobs/test_llmobs_trace_processor.py +++ b/tests/llmobs/test_llmobs_trace_processor.py @@ -6,6 +6,7 @@ from ddtrace.llmobs._constants import INPUT_MESSAGES from ddtrace.llmobs._constants import INPUT_PARAMETERS from ddtrace.llmobs._constants import INPUT_VALUE +from ddtrace.llmobs._constants import LANGCHAIN_APM_SPAN_NAME from ddtrace.llmobs._constants import METADATA from ddtrace.llmobs._constants import METRICS from ddtrace.llmobs._constants import ML_APP @@ -360,6 +361,17 @@ def test_metrics_are_set(): assert tp._llmobs_span_event(llm_span)["metrics"] == {"tokens": 100} +def test_langchain_span_name_is_set_to_class_name(): + """Test span names for langchain auto-instrumented spans is set correctly.""" + dummy_tracer = DummyTracer() + mock_llmobs_span_writer = mock.MagicMock() + with override_global_config(dict(_llmobs_ml_app="unnamed-ml-app")): + with dummy_tracer.trace(LANGCHAIN_APM_SPAN_NAME, resource="expected_name", span_type=SpanTypes.LLM) as llm_span: + llm_span.set_tag(SPAN_KIND, "llm") + tp = LLMObsTraceProcessor(llmobs_span_writer=mock_llmobs_span_writer) + assert tp._llmobs_span_event(llm_span)["name"] == "expected_name" + + def test_error_is_set(): """Test that error is set on the span event if it is present on the span.""" dummy_tracer = DummyTracer() From 31ca1627c1e94d4837bab54fd0daea360a474f38 Mon Sep 17 00:00:00 2001 From: erikayasuda <153395705+erikayasuda@users.noreply.github.com> Date: Wed, 22 May 2024 12:54:18 -0400 Subject: [PATCH 096/104] chore(flake): unflake tracer flare tests (#9339) ## Overview The tracer flare tests have been known to be a bit flaky on CircleCI, mainly due to the I/O operations that get tested. I think CircleCI has to make network calls to do these, so they pass locally but can flake remotely. Changed the tests to use `pyfakefs` so this is all in-memory now, and should behave the same locally as it does on CircleCI. Also did some refactoring and additional clean up of the tests so logs are cleaner and exceptions in multiprocess tests are actually exposed. ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .../requirements/{4cba75c.txt => 1338800.txt} | 11 +- .../requirements/{b1a336f.txt => 17141ca.txt} | 23 ++- .../requirements/{1902e9f.txt => 17d7a61.txt} | 21 +- .../requirements/{1130e9d.txt => 1b61411.txt} | 27 +-- .../requirements/{99214a1.txt => 1bb5be8.txt} | 27 +-- .../requirements/{112818b.txt => 84620ce.txt} | 21 +- riotfile.py | 1 + tests/internal/test_packages.py | 2 + tests/internal/test_tracer_flare.py | 195 +++++++++++------- 9 files changed, 197 insertions(+), 131 deletions(-) rename .riot/requirements/{4cba75c.txt => 1338800.txt} (81%) rename .riot/requirements/{b1a336f.txt => 17141ca.txt} (60%) rename .riot/requirements/{1902e9f.txt => 17d7a61.txt} (62%) rename .riot/requirements/{1130e9d.txt => 1b61411.txt} (57%) rename .riot/requirements/{99214a1.txt => 1bb5be8.txt} (57%) rename .riot/requirements/{112818b.txt => 84620ce.txt} (62%) diff --git a/.riot/requirements/4cba75c.txt b/.riot/requirements/1338800.txt similarity index 81% rename from .riot/requirements/4cba75c.txt rename to .riot/requirements/1338800.txt index 0aa5339625a..5fb0a89fd83 100644 --- a/.riot/requirements/4cba75c.txt +++ b/.riot/requirements/1338800.txt @@ -2,11 +2,11 @@ # This file is autogenerated by pip-compile with Python 3.7 # by the following command: # -# pip-compile --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/4cba75c.in +# pip-compile --config=pyproject.toml --no-annotate --resolver=backtracking .riot/requirements/1338800.in # attrs==23.2.0 coverage[toml]==7.2.7 -exceptiongroup==1.2.0 +exceptiongroup==1.2.1 gevent==22.10.2 greenlet==3.0.3 httpretty==1.1.4 @@ -15,10 +15,11 @@ importlib-metadata==6.7.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==23.2 +packaging==24.0 pluggy==1.2.0 +pyfakefs==5.5.0 pytest==7.4.4 -pytest-asyncio==0.21.1 +pytest-asyncio==0.21.2 pytest-cov==4.1.0 pytest-mock==3.11.1 pytest-randomly==3.12.0 @@ -28,7 +29,7 @@ tomli==2.0.1 typing-extensions==4.7.1 zipp==3.15.0 zope-event==5.0 -zope-interface==6.1 +zope-interface==6.4 # The following packages are considered to be unsafe in a requirements file: # setuptools diff --git a/.riot/requirements/b1a336f.txt b/.riot/requirements/17141ca.txt similarity index 60% rename from .riot/requirements/b1a336f.txt rename to .riot/requirements/17141ca.txt index 6acea8d9c8a..e6140c614fd 100644 --- a/.riot/requirements/b1a336f.txt +++ b/.riot/requirements/17141ca.txt @@ -2,30 +2,31 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --no-annotate .riot/requirements/b1a336f.in +# pip-compile --no-annotate .riot/requirements/17141ca.in # attrs==23.2.0 -coverage[toml]==7.4.0 -exceptiongroup==1.2.0 -gevent==23.9.1 +coverage[toml]==7.5.1 +exceptiongroup==1.2.1 +gevent==24.2.1 greenlet==3.0.3 httpretty==1.1.4 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.4 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 +packaging==24.0 +pluggy==1.5.0 +pyfakefs==5.5.0 +pytest==8.2.1 +pytest-asyncio==0.21.2 +pytest-cov==5.0.0 +pytest-mock==3.14.0 pytest-randomly==3.15.0 python-json-logger==2.0.7 sortedcontainers==2.4.0 tomli==2.0.1 zope-event==5.0 -zope-interface==6.1 +zope-interface==6.4 # The following packages are considered to be unsafe in a requirements file: # setuptools diff --git a/.riot/requirements/1902e9f.txt b/.riot/requirements/17d7a61.txt similarity index 62% rename from .riot/requirements/1902e9f.txt rename to .riot/requirements/17d7a61.txt index ba42d18ddcf..cd7fec9785b 100644 --- a/.riot/requirements/1902e9f.txt +++ b/.riot/requirements/17d7a61.txt @@ -2,28 +2,29 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --no-annotate .riot/requirements/1902e9f.in +# pip-compile --no-annotate .riot/requirements/17d7a61.in # attrs==23.2.0 -coverage[toml]==7.4.0 -gevent==23.9.1 +coverage[toml]==7.5.1 +gevent==24.2.1 greenlet==3.0.3 httpretty==1.1.4 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.4 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 +packaging==24.0 +pluggy==1.5.0 +pyfakefs==5.5.0 +pytest==8.2.1 +pytest-asyncio==0.21.2 +pytest-cov==5.0.0 +pytest-mock==3.14.0 pytest-randomly==3.15.0 python-json-logger==2.0.7 sortedcontainers==2.4.0 zope-event==5.0 -zope-interface==6.1 +zope-interface==6.4 # The following packages are considered to be unsafe in a requirements file: # setuptools diff --git a/.riot/requirements/1130e9d.txt b/.riot/requirements/1b61411.txt similarity index 57% rename from .riot/requirements/1130e9d.txt rename to .riot/requirements/1b61411.txt index a2eec10ccbf..d260abc3c79 100644 --- a/.riot/requirements/1130e9d.txt +++ b/.riot/requirements/1b61411.txt @@ -2,32 +2,33 @@ # This file is autogenerated by pip-compile with Python 3.8 # by the following command: # -# pip-compile --no-annotate .riot/requirements/1130e9d.in +# pip-compile --no-annotate .riot/requirements/1b61411.in # attrs==23.2.0 -coverage[toml]==7.4.0 -exceptiongroup==1.2.0 -gevent==23.9.1 +coverage[toml]==7.5.1 +exceptiongroup==1.2.1 +gevent==24.2.1 greenlet==3.0.3 httpretty==1.1.4 hypothesis==6.45.0 -importlib-metadata==7.0.1 +importlib-metadata==7.1.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.4 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 +packaging==24.0 +pluggy==1.5.0 +pyfakefs==5.5.0 +pytest==8.2.1 +pytest-asyncio==0.21.2 +pytest-cov==5.0.0 +pytest-mock==3.14.0 pytest-randomly==3.15.0 python-json-logger==2.0.7 sortedcontainers==2.4.0 tomli==2.0.1 -zipp==3.17.0 +zipp==3.18.2 zope-event==5.0 -zope-interface==6.1 +zope-interface==6.4 # The following packages are considered to be unsafe in a requirements file: # setuptools diff --git a/.riot/requirements/99214a1.txt b/.riot/requirements/1bb5be8.txt similarity index 57% rename from .riot/requirements/99214a1.txt rename to .riot/requirements/1bb5be8.txt index e30a32a6a1b..0cf613fc4e8 100644 --- a/.riot/requirements/99214a1.txt +++ b/.riot/requirements/1bb5be8.txt @@ -2,32 +2,33 @@ # This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --no-annotate .riot/requirements/99214a1.in +# pip-compile --no-annotate .riot/requirements/1bb5be8.in # attrs==23.2.0 -coverage[toml]==7.4.0 -exceptiongroup==1.2.0 -gevent==23.9.1 +coverage[toml]==7.5.1 +exceptiongroup==1.2.1 +gevent==24.2.1 greenlet==3.0.3 httpretty==1.1.4 hypothesis==6.45.0 -importlib-metadata==7.0.1 +importlib-metadata==7.1.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.4 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 +packaging==24.0 +pluggy==1.5.0 +pyfakefs==5.5.0 +pytest==8.2.1 +pytest-asyncio==0.21.2 +pytest-cov==5.0.0 +pytest-mock==3.14.0 pytest-randomly==3.15.0 python-json-logger==2.0.7 sortedcontainers==2.4.0 tomli==2.0.1 -zipp==3.17.0 +zipp==3.18.2 zope-event==5.0 -zope-interface==6.1 +zope-interface==6.4 # The following packages are considered to be unsafe in a requirements file: # setuptools diff --git a/.riot/requirements/112818b.txt b/.riot/requirements/84620ce.txt similarity index 62% rename from .riot/requirements/112818b.txt rename to .riot/requirements/84620ce.txt index 31835b02454..4fb324487fc 100644 --- a/.riot/requirements/112818b.txt +++ b/.riot/requirements/84620ce.txt @@ -2,28 +2,29 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --no-annotate .riot/requirements/112818b.in +# pip-compile --no-annotate .riot/requirements/84620ce.in # attrs==23.2.0 -coverage[toml]==7.4.0 -gevent==23.9.1 +coverage[toml]==7.5.1 +gevent==24.2.1 greenlet==3.0.3 httpretty==1.1.4 hypothesis==6.45.0 iniconfig==2.0.0 mock==5.1.0 opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.4 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 +packaging==24.0 +pluggy==1.5.0 +pyfakefs==5.5.0 +pytest==8.2.1 +pytest-asyncio==0.21.2 +pytest-cov==5.0.0 +pytest-mock==3.14.0 pytest-randomly==3.15.0 python-json-logger==2.0.7 sortedcontainers==2.4.0 zope-event==5.0 -zope-interface==6.1 +zope-interface==6.4 # The following packages are considered to be unsafe in a requirements file: # setuptools diff --git a/riotfile.py b/riotfile.py index 7ec9baab4e8..498f1a3424e 100644 --- a/riotfile.py +++ b/riotfile.py @@ -446,6 +446,7 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION): "pytest-asyncio": "~=0.21.1", "pytest-randomly": latest, "python-json-logger": "==2.0.7", + "pyfakefs": latest, }, pys=select_pys(min_version="3.7", max_version="3.12"), ), diff --git a/tests/internal/test_packages.py b/tests/internal/test_packages.py index 763504159a2..67ee726cc23 100644 --- a/tests/internal/test_packages.py +++ b/tests/internal/test_packages.py @@ -52,6 +52,8 @@ def test_get_distributions(): importlib_pkgs.add("typing-extensions") elif pkg.name == "pkgutil_resolve_name" and "pkgutil-resolve-name" in pkg_resources_ws: importlib_pkgs.add("pkgutil-resolve-name") + elif pkg.name == "importlib_metadata" and "importlib-metadata" in pkg_resources_ws: + importlib_pkgs.add("importlib-metadata") else: importlib_pkgs.add(pkg.name) diff --git a/tests/internal/test_tracer_flare.py b/tests/internal/test_tracer_flare.py index ef85482197f..af8237c92e4 100644 --- a/tests/internal/test_tracer_flare.py +++ b/tests/internal/test_tracer_flare.py @@ -4,43 +4,47 @@ import os import pathlib from typing import Optional -import unittest from unittest import mock -import uuid + +from pyfakefs.fake_filesystem_unittest import TestCase from ddtrace.internal.flare._subscribers import TracerFlareSubscriber -from ddtrace.internal.flare.flare import TRACER_FLARE_DIRECTORY from ddtrace.internal.flare.flare import TRACER_FLARE_FILE_HANDLER_NAME from ddtrace.internal.flare.flare import Flare from ddtrace.internal.flare.flare import FlareSendRequest from ddtrace.internal.flare.handler import _handle_tracer_flare from ddtrace.internal.logger import get_logger from ddtrace.internal.remoteconfig._connectors import PublisherSubscriberConnector -from tests.utils import flaky DEBUG_LEVEL_INT = logging.DEBUG TRACE_AGENT_URL = "http://localhost:9126" +MOCK_FLARE_SEND_REQUEST = FlareSendRequest(case_id="1111111", hostname="myhostname", email="user.name@datadoghq.com") -class TracerFlareTests(unittest.TestCase): - mock_flare_send_request = FlareSendRequest( - case_id="1111111", hostname="myhostname", email="user.name@datadoghq.com" - ) - +class TracerFlareTests(TestCase): mock_config_dict = {} def setUp(self): - self.flare_uuid = uuid.uuid4() - self.flare_dir = f"{TRACER_FLARE_DIRECTORY}-{self.flare_uuid}" + self.setUpPyfakefs() + self.shared_dir = self.fs.create_dir("tracer_flare_test") self.flare = Flare( - trace_agent_url=TRACE_AGENT_URL, flare_dir=pathlib.Path(self.flare_dir), ddconfig={"config": "testconfig"} + trace_agent_url=TRACE_AGENT_URL, + flare_dir=pathlib.Path(self.shared_dir.name), + ddconfig={"config": "testconfig"}, ) self.pid = os.getpid() - self.flare_file_path = f"{self.flare_dir}/tracer_python_{self.pid}.log" - self.config_file_path = f"{self.flare_dir}/tracer_config_{self.pid}.json" + self.flare_file_path = f"{self.shared_dir.name}/tracer_python_{self.pid}.log" + self.config_file_path = f"{self.shared_dir.name}/tracer_config_{self.pid}.json" def tearDown(self): + try: + self.shared_dir.cleanup() + except Exception: + # This will always fail because our Flare logic cleans up the entire directory + # We're explicitly calling this in tearDown so that we can remove + # the error log clutter for python < 3.10 + pass self.confirm_cleanup() def _get_handler(self) -> Optional[logging.Handler]: @@ -70,7 +74,7 @@ def test_single_process_success(self): # Sends request to testagent # This just validates the request params - self.flare.send(self.mock_flare_send_request) + self.flare.send(MOCK_FLARE_SEND_REQUEST) def test_single_process_partial_failure(self): """ @@ -93,7 +97,38 @@ def test_single_process_partial_failure(self): assert os.path.exists(self.flare_file_path) assert not os.path.exists(self.config_file_path) - self.flare.send(self.mock_flare_send_request) + self.flare.send(MOCK_FLARE_SEND_REQUEST) + + def test_no_app_logs(self): + """ + Validate that app logs are not being added to the + file, just the tracer logs + """ + app_logger = Logger(name="my-app", level=DEBUG_LEVEL_INT) + self.flare.prepare("DEBUG") + + app_log_line = "this is an app log" + app_logger.debug(app_log_line) + + assert os.path.exists(self.flare_file_path) + + with open(self.flare_file_path, "r") as file: + for line in file: + assert app_log_line not in line, f"File {self.flare_file_path} contains excluded line: {app_log_line}" + + self.flare.clean_up_files() + self.flare.revert_configs() + + def confirm_cleanup(self): + assert not self.flare.flare_dir.exists(), f"The directory {self.flare.flare_dir} still exists" + assert self._get_handler() is None, "File handler was not removed" + + +class TracerFlareMultiprocessTests(TestCase): + def setUp(self): + self.setUpPyfakefs() + self.shared_dir = self.fs.create_dir("tracer_flare_test") + self.errors = multiprocessing.Queue() def test_multiple_process_success(self): """ @@ -101,84 +136,104 @@ def test_multiple_process_success(self): """ processes = [] num_processes = 3 - - def handle_agent_config(): - self.flare.prepare("DEBUG") - - def handle_agent_task(): - self.flare.send(self.mock_flare_send_request) + flares = [] + for _ in range(num_processes): + flares.append( + Flare( + trace_agent_url=TRACE_AGENT_URL, + flare_dir=pathlib.Path(self.shared_dir.name), + ddconfig={"config": "testconfig"}, + ) + ) + + def handle_agent_config(flare: Flare): + try: + flare.prepare("DEBUG") + # Assert that each process wrote its file successfully + # We double the process number because each will generate a log file and a config file + if len(os.listdir(self.shared_dir.name)) == 0: + self.errors.put(Exception("Files were not generated")) + except Exception as e: + self.errors.put(e) + + def handle_agent_task(flare: Flare): + try: + flare.send(MOCK_FLARE_SEND_REQUEST) + if os.path.exists(self.shared_dir.name): + self.errors.put(Exception("Directory was not cleaned up")) + except Exception as e: + self.errors.put(e) # Create multiple processes - for _ in range(num_processes): - p = multiprocessing.Process(target=handle_agent_config) + for i in range(num_processes): + flare = flares[i] + p = multiprocessing.Process(target=handle_agent_config, args=(flare,)) processes.append(p) p.start() for p in processes: p.join() - # Assert that each process wrote its file successfully - # We double the process number because each will generate a log file and a config file - assert len(processes) * 2 == len(os.listdir(self.flare_dir)) - - for _ in range(num_processes): - p = multiprocessing.Process(target=handle_agent_task) + for i in range(num_processes): + flare = flares[i] + p = multiprocessing.Process(target=handle_agent_task, args=(flare,)) processes.append(p) p.start() for p in processes: p.join() - @flaky(1722529274) + assert self.errors.qsize() == 0 + def test_multiple_process_partial_failure(self): """ Validte that even if the tracer flare fails for one process, we should still continue the work for the other processes (ensure best effort) """ processes = [] - - def do_tracer_flare(prep_request, send_request): - self.flare.prepare(prep_request) - # Assert that only one process wrote its file successfully - # We check for 2 files because it will generate a log file and a config file - assert 2 == len(os.listdir(self.flare_dir)) - self.flare.send(send_request) + flares = [] + for _ in range(2): + flares.append( + Flare( + trace_agent_url=TRACE_AGENT_URL, + flare_dir=pathlib.Path(self.shared_dir.name), + ddconfig={"config": "testconfig"}, + ) + ) + + def do_tracer_flare(log_level: str, send_request: FlareSendRequest, flare: Flare): + try: + flare.prepare(log_level) + # Assert that only one process wrote its file successfully + # We check for 2 files because it will generate a log file and a config file + assert 2 == len(os.listdir(self.shared_dir.name)) + flare.send(send_request) + except Exception as e: + self.errors.put(e) # Create successful process - p = multiprocessing.Process(target=do_tracer_flare, args=("DEBUG", self.mock_flare_send_request)) + p = multiprocessing.Process(target=do_tracer_flare, args=("DEBUG", MOCK_FLARE_SEND_REQUEST, flares[0])) processes.append(p) p.start() # Create failing process - p = multiprocessing.Process(target=do_tracer_flare, args=(None, self.mock_flare_send_request)) + p = multiprocessing.Process(target=do_tracer_flare, args=(None, MOCK_FLARE_SEND_REQUEST, flares[1])) processes.append(p) p.start() for p in processes: p.join() + assert self.errors.qsize() == 1 - def test_no_app_logs(self): - """ - Validate that app logs are not being added to the - file, just the tracer logs - """ - app_logger = Logger(name="my-app", level=DEBUG_LEVEL_INT) - self.flare.prepare("DEBUG") - - app_log_line = "this is an app log" - app_logger.debug(app_log_line) - assert os.path.exists(self.flare_file_path) - - with open(self.flare_file_path, "r") as file: - for line in file: - assert app_log_line not in line, f"File {self.flare_file_path} contains excluded line: {app_log_line}" +class MockPubSubConnector(PublisherSubscriberConnector): + def __init__(self): + pass - self.flare.clean_up_files() - self.flare.revert_configs() + def read(self): + pass - def confirm_cleanup(self): - assert not self.flare.flare_dir.exists(), f"The directory {self.flare.flare_dir} still exists" - assert self._get_handler() is None, "File handler was not removed" + def write(self): + pass -class TracerFlareSubscriberTests(unittest.TestCase): +class TracerFlareSubscriberTests(TestCase): agent_config = [{"name": "flare-log-level", "config": {"log_level": "DEBUG"}}] agent_task = [ False, @@ -194,16 +249,20 @@ class TracerFlareSubscriberTests(unittest.TestCase): ] def setUp(self): + self.setUpPyfakefs() + self.shared_dir = self.fs.create_dir("tracer_flare_test") self.tracer_flare_sub = TracerFlareSubscriber( - data_connector=PublisherSubscriberConnector(), + data_connector=MockPubSubConnector(), callback=_handle_tracer_flare, - flare=Flare(trace_agent_url=TRACE_AGENT_URL, ddconfig={"config": "testconfig"}), + flare=Flare( + trace_agent_url=TRACE_AGENT_URL, + ddconfig={"config": "testconfig"}, + flare_dir=pathlib.Path(self.shared_dir.name), + ), ) def generate_agent_config(self): - with mock.patch( - "ddtrace.internal.remoteconfig._connectors.PublisherSubscriberConnector.read" - ) as mock_pubsub_conn: + with mock.patch("tests.internal.test_tracer_flare.MockPubSubConnector.read") as mock_pubsub_conn: mock_pubsub_conn.return_value = { "metadata": [{"product_name": "AGENT_CONFIG"}], "config": self.agent_config, @@ -211,9 +270,7 @@ def generate_agent_config(self): self.tracer_flare_sub._get_data_from_connector_and_exec() def generate_agent_task(self): - with mock.patch( - "ddtrace.internal.remoteconfig._connectors.PublisherSubscriberConnector.read" - ) as mock_pubsub_conn: + with mock.patch("tests.internal.test_tracer_flare.MockPubSubConnector.read") as mock_pubsub_conn: mock_pubsub_conn.return_value = { "metadata": [{"product_name": "AGENT_TASK"}], "config": self.agent_task, From 9d73b9353650d64be480f7f8a82f0a0eab26d017 Mon Sep 17 00:00:00 2001 From: erikayasuda <153395705+erikayasuda@users.noreply.github.com> Date: Wed, 22 May 2024 13:31:44 -0400 Subject: [PATCH 097/104] chore(flake): marking `test_service_name` as flaky (#9353) Marking flaky tests exposed from running CI on another PR: https://github.com/DataDog/dd-trace-py/pull/9339 ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [ ] Title is accurate - [ ] All changes are related to the pull request's stated goal - [ ] Description motivates each change - [ ] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [ ] Testing strategy adequately addresses listed risks - [ ] Change is maintainable (easy to change, telemetry, documentation) - [ ] Release note makes sense to a user of the library - [ ] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [ ] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- tests/internal/service_name/test_extra_services_names.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/internal/service_name/test_extra_services_names.py b/tests/internal/service_name/test_extra_services_names.py index 719e32935e1..6102b93330b 100644 --- a/tests/internal/service_name/test_extra_services_names.py +++ b/tests/internal/service_name/test_extra_services_names.py @@ -5,11 +5,13 @@ import pytest import ddtrace +from tests.utils import flaky MAX_NAMES = 64 +@flaky(1735812000) @pytest.mark.parametrize("nb_service", [2, 16, 64, 256]) def test_service_name(nb_service): ddtrace.config._extra_services = set() From 6c0e8d84017dcdff0bf3b72ec54d8463a101d1e5 Mon Sep 17 00:00:00 2001 From: Zarir Hamza Date: Wed, 22 May 2024 14:46:53 -0400 Subject: [PATCH 098/104] feat(otel): adds compatibility for trace metrics via otel spans (#9355) ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- ddtrace/opentelemetry/_span.py | 1 + ...etrics-compatability-f213b8aa2e90db22.yaml | 4 +++ tests/opentelemetry/test_span.py | 1 + ..._span_attributes_overrides[override5].json | 26 +++++++++++++++++++ 4 files changed, 32 insertions(+) create mode 100644 releasenotes/notes/otel-trace-metrics-compatability-f213b8aa2e90db22.yaml create mode 100644 tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes_overrides[override5].json diff --git a/ddtrace/opentelemetry/_span.py b/ddtrace/opentelemetry/_span.py index 5da7c31ba23..ecd50f6dbac 100644 --- a/ddtrace/opentelemetry/_span.py +++ b/ddtrace/opentelemetry/_span.py @@ -48,6 +48,7 @@ def _ddmap(span, attribute, value): "resource.name": "resource", "span.type": "span_type", "analytics.event": "metrics['_dd1.sr.eausr']", + "http.response.status_code": "meta['http.status_code']", } diff --git a/releasenotes/notes/otel-trace-metrics-compatability-f213b8aa2e90db22.yaml b/releasenotes/notes/otel-trace-metrics-compatability-f213b8aa2e90db22.yaml new file mode 100644 index 00000000000..3f23c7bbd9f --- /dev/null +++ b/releasenotes/notes/otel-trace-metrics-compatability-f213b8aa2e90db22.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + otel: adds support for generating Datadog trace metrics using OpenTelemetry instrumentations diff --git a/tests/opentelemetry/test_span.py b/tests/opentelemetry/test_span.py index 91dd2c9fa58..04db83debec 100644 --- a/tests/opentelemetry/test_span.py +++ b/tests/opentelemetry/test_span.py @@ -55,6 +55,7 @@ def test_otel_span_attributes(oteltracer): ("resource.name", "resource-override"), ("span.type", "type-override"), ("analytics.event", 0.5), + ("http.response.status_code", 200), ], ) def test_otel_span_attributes_overrides(oteltracer, override): diff --git a/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes_overrides[override5].json b/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes_overrides[override5].json new file mode 100644 index 00000000000..d346194b8e7 --- /dev/null +++ b/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes_overrides[override5].json @@ -0,0 +1,26 @@ +[[ + { + "name": "internal", + "service": "", + "resource": "set-http.response.status_code", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "", + "error": 0, + "meta": { + "_dd.p.dm": "-0", + "_dd.p.tid": "664e2ebd00000000", + "http.status_code": "200", + "language": "python", + "runtime-id": "51219e893c6e4aa78650c25d19abae25" + }, + "metrics": { + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 4723 + }, + "duration": 98584, + "start": 1716399805807262166 + }]] From 0cd68988409888a4a36b9bd18b603e302b5e3235 Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Wed, 22 May 2024 16:38:22 -0400 Subject: [PATCH 099/104] fix(w3c): ensure tracecontext headers take precedence over datadog (#9254) ## Description Ensures when trace headers with the same trace_id but different span_ids are received by an application the tracecontext headers are treated as the source of truth. - The traceparent header will be used to set the parent_id - If the conflicting trace information was extracted from datadog headers, the span_id from the datadog headers will be stored in the _dd.parent_id parent_id tag. - All other propagation styles are third class citizens. If they conflict with tracecontext headers their span_ids will be overwritten. This behavior will be validated by the following system-tests: https://github.com/DataDog/system-tests/pull/2385 ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Brett Langdon --- ddtrace/internal/constants.py | 1 + ddtrace/propagation/http.py | 13 ++++++++++ .../w3c-phase-3-take2-bd8905504d1420d9.yaml | 5 ++++ tests/tracer/test_propagation.py | 26 ++++++++++++++++--- 4 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/w3c-phase-3-take2-bd8905504d1420d9.yaml diff --git a/ddtrace/internal/constants.py b/ddtrace/internal/constants.py index 50b8e1280e4..7988687a74c 100644 --- a/ddtrace/internal/constants.py +++ b/ddtrace/internal/constants.py @@ -25,6 +25,7 @@ DEFAULT_SAMPLING_RATE_LIMIT = 100 SAMPLING_DECISION_TRACE_TAG_KEY = "_dd.p.dm" LAST_DD_PARENT_ID_KEY = "_dd.parent_id" +DEFAULT_LAST_PARENT_ID = "0000000000000000" DEFAULT_SERVICE_NAME = "unnamed-python-service" # Used to set the name of an integration on a span COMPONENT = "component" diff --git a/ddtrace/propagation/http.py b/ddtrace/propagation/http.py index c0768194d8b..12c6026750d 100644 --- a/ddtrace/propagation/http.py +++ b/ddtrace/propagation/http.py @@ -38,6 +38,7 @@ from ..internal.compat import ensure_text from ..internal.constants import _PROPAGATION_STYLE_NONE from ..internal.constants import _PROPAGATION_STYLE_W3C_TRACECONTEXT +from ..internal.constants import DEFAULT_LAST_PARENT_ID from ..internal.constants import HIGHER_ORDER_TRACE_ID_BITS as _HIGHER_ORDER_TRACE_ID_BITS from ..internal.constants import LAST_DD_PARENT_ID_KEY from ..internal.constants import MAX_UINT_64BITS as _MAX_UINT_64BITS @@ -921,6 +922,18 @@ def _resolve_contexts(contexts, styles_w_ctx, normalized_headers): ts = _extract_header_value(_POSSIBLE_HTTP_HEADER_TRACESTATE, normalized_headers) if ts: primary_context._meta[W3C_TRACESTATE_KEY] = ts + if primary_context.trace_id == context.trace_id and primary_context.span_id != context.span_id: + dd_context = None + if PROPAGATION_STYLE_DATADOG in styles_w_ctx: + dd_context = contexts[styles_w_ctx.index(PROPAGATION_STYLE_DATADOG)] + if context._meta.get(LAST_DD_PARENT_ID_KEY, DEFAULT_LAST_PARENT_ID) != DEFAULT_LAST_PARENT_ID: + # tracecontext headers contain a p value, ensure this value is sent to backend + primary_context._meta[LAST_DD_PARENT_ID_KEY] = context._meta[LAST_DD_PARENT_ID_KEY] + elif dd_context: + # if p value is not present in tracestate, use the parent id from the datadog headers + primary_context._meta[LAST_DD_PARENT_ID_KEY] = "{:016x}".format(dd_context.span_id) + # the span_id in tracecontext takes precedence over the first extracted propagation style + primary_context.span_id = context.span_id primary_context._span_links = links return primary_context diff --git a/releasenotes/notes/w3c-phase-3-take2-bd8905504d1420d9.yaml b/releasenotes/notes/w3c-phase-3-take2-bd8905504d1420d9.yaml new file mode 100644 index 00000000000..4fa4aa67bdf --- /dev/null +++ b/releasenotes/notes/w3c-phase-3-take2-bd8905504d1420d9.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + tracing: Ensures W3C tracecontext headers take precedence over all other header formats when incoming headers + reference different spans in the same trace. diff --git a/tests/tracer/test_propagation.py b/tests/tracer/test_propagation.py index e6c263e5d83..b7b6013fc21 100644 --- a/tests/tracer/test_propagation.py +++ b/tests/tracer/test_propagation.py @@ -1751,10 +1751,14 @@ def test_extract_tracecontext(headers, expected_context): DATADOG_TRACECONTEXT_MATCHING_TRACE_ID_HEADERS, { "trace_id": _get_64_lowest_order_bits_as_int(TRACE_ID), - "span_id": 5678, + "span_id": 67667974448284343, "sampling_priority": 1, "dd_origin": "synthetics", - "meta": {"tracestate": TRACECONTEXT_HEADERS_VALID[_HTTP_HEADER_TRACESTATE], "_dd.p.dm": "-3"}, + "meta": { + "tracestate": TRACECONTEXT_HEADERS_VALID[_HTTP_HEADER_TRACESTATE], + "_dd.p.dm": "-3", + LAST_DD_PARENT_ID_KEY: "000000000000162e", + }, }, ), # testing that tracestate is not added when tracecontext style comes later and does not match first style's trace-id @@ -1791,6 +1795,21 @@ def test_extract_tracecontext(headers, expected_context): {get_wsgi_header(name): value for name, value in ALL_HEADERS.items()}, CONTEXT_EMPTY, ), + ( + "datadog_tracecontext_conflicting_span_ids", + [PROPAGATION_STYLE_DATADOG, _PROPAGATION_STYLE_W3C_TRACECONTEXT], + { + HTTP_HEADER_TRACE_ID: "9291375655657946024", + HTTP_HEADER_PARENT_ID: "15", + _HTTP_HEADER_TRACEPARENT: "00-000000000000000080f198ee56343ba8-000000000000000a-01", + }, + { + "trace_id": 9291375655657946024, + "span_id": 10, + "sampling_priority": 2, + "meta": {"_dd.p.dm": "-3", LAST_DD_PARENT_ID_KEY: "000000000000000f"}, + }, + ), ] # Only add fixtures here if they can't pass both test_propagation_extract_env @@ -2110,7 +2129,7 @@ def test_DD_TRACE_PROPAGATION_STYLE_EXTRACT_overrides_DD_TRACE_PROPAGATION_STYLE ALL_HEADERS_CHAOTIC_1, Context( trace_id=7277407061855694839, - span_id=5678, + span_id=67667974448284343, # it's weird that both _dd.p.dm and tracestate.t.dm are set here. as far as i know, this is the expected # behavior for this chaotic set of headers, specifically when STYLE_DATADOG precedes STYLE_W3C_TRACECONTEXT # in the styles configuration @@ -2118,6 +2137,7 @@ def test_DD_TRACE_PROPAGATION_STYLE_EXTRACT_overrides_DD_TRACE_PROPAGATION_STYLE "_dd.p.dm": "-3", "_dd.origin": "synthetics", "tracestate": "dd=s:2;o:rum;t.dm:-4;t.usr.id:baz64,congo=t61rcWkgMzE", + LAST_DD_PARENT_ID_KEY: "000000000000162e", }, metrics={"_sampling_priority_v1": 1}, span_links=[ From f471e6bf24b6cb71dfa3f33cb094562b1e7e9c8a Mon Sep 17 00:00:00 2001 From: ddmclav <99694116+ddmclav@users.noreply.github.com> Date: Wed, 22 May 2024 22:51:54 -0400 Subject: [PATCH 100/104] feat(otel): support mapping otel environment variables to ddtrace environment variables (#9298) ## Description This provides mapping support between values set for OTEL environment variables to Datadog equivalent tracing environment variables. ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: Munir Abdinur Co-authored-by: Munir Abdinur Co-authored-by: Brett Langdon --- ddtrace/settings/_otel_remapper.py | 191 +++++++++++ ddtrace/settings/config.py | 5 +- ...port-for-dd-trace-py-d4378445a1405bab.yaml | 15 + tests/opentelemetry/test_config.py | 313 ++++++++++++++++++ 4 files changed, 523 insertions(+), 1 deletion(-) create mode 100644 ddtrace/settings/_otel_remapper.py create mode 100644 releasenotes/notes/otel-env-var-support-for-dd-trace-py-d4378445a1405bab.yaml create mode 100644 tests/opentelemetry/test_config.py diff --git a/ddtrace/settings/_otel_remapper.py b/ddtrace/settings/_otel_remapper.py new file mode 100644 index 00000000000..1b9dffe9fae --- /dev/null +++ b/ddtrace/settings/_otel_remapper.py @@ -0,0 +1,191 @@ +import os +import sys + +from ..constants import ENV_KEY +from ..constants import VERSION_KEY +from ..internal.logger import get_logger + + +if sys.version_info >= (3, 8): + import typing +else: + import typing_extensions as typing # noqa: F401 + +log = get_logger(__name__) + + +OTEL_UNIFIED_TAG_MAPPINGS = { + "deployment.environment": ENV_KEY, + "service.name": "service", + "service.version": VERSION_KEY, +} + + +def _remap_otel_log_level(otel_value: str) -> str: + """Remaps the otel log level to ddtrace log level""" + if otel_value == "debug": + return "True" + else: + log.warning( + "ddtrace does not support otel log level '%s'. ddtrace only supports enabling debug logs.", + otel_value, + ) + return "False" + + +def _remap_otel_propagators(otel_value: str) -> str: + """Remaps the otel propagators to ddtrace propagators""" + accepted_styles = [] + for style in otel_value.split(","): + style = style.strip().lower() + if style in ["b3", "b3multi", "datadog", "tracecontext", "none"]: + if style not in accepted_styles: + accepted_styles.append(style) + else: + log.warning("Following style not supported by ddtrace: %s.", style) + return ",".join(accepted_styles) + + +def _remap_traces_sampler(otel_value: str) -> str: + """Remaps the otel trace sampler to ddtrace trace sampler""" + if otel_value in ["always_on", "always_off", "traceidratio"]: + log.warning( + "Trace sampler set from %s to parentbased_%s; only parent based sampling is supported.", + otel_value, + otel_value, + ) + otel_value = f"parentbased_{otel_value}" + if otel_value == "parentbased_always_on": + return "1.0" + elif otel_value == "parentbased_always_off": + return "0.0" + elif otel_value == "parentbased_traceidratio": + return os.environ.get("OTEL_TRACES_SAMPLER_ARG", "1") + else: + log.warning("Unknown sampling configuration: %s.", otel_value) + return otel_value + + +def _remap_traces_exporter(otel_value: str) -> str: + """Remaps the otel trace exporter to ddtrace trace enabled""" + if otel_value == "none": + return "False" + log.warning( + "A trace exporter value '%s' is set, but not supported. Traces will be exported to Datadog.", otel_value + ) + return "" + + +def _remap_metrics_exporter(otel_value: str) -> str: + """Remaps the otel metrics exporter to ddtrace metrics exporter""" + if otel_value == "none": + return "False" + log.warning( + "Metrics exporter value is set to unrecognized value: %s.", + otel_value, + ) + return "" + + +def _validate_logs_exporter(otel_value: str) -> typing.Literal[""]: + """Logs warning when OTEL Logs exporter is configured. DDTRACE does not support this configuration.""" + if otel_value != "none": + log.warning( + "Unsupported OTEL logs exporter value detected: %s. Only the 'none' value is supported.", otel_value + ) + return "" + + +def _remap_otel_tags(otel_value: str) -> str: + """Remaps the otel tags to ddtrace tags""" + dd_tags: typing.List[str] = [] + + try: + otel_user_tag_dict: typing.Dict[str, str] = dict() + for tag in otel_value.split(","): + key, value = tag.split("=") + otel_user_tag_dict[key] = value + + for key, value in otel_user_tag_dict.items(): + if key.lower() in OTEL_UNIFIED_TAG_MAPPINGS: + dd_key = OTEL_UNIFIED_TAG_MAPPINGS[key.lower()] + dd_tags.insert(0, f"{dd_key}:{value}") + else: + dd_tags.append(f"{key}:{value}") + except Exception: + log.warning("DDTRACE failed to read OTEL_RESOURCE_ATTRIBUTES. This value is misformatted: %s", otel_value) + + if len(dd_tags) > 10: + dd_tags, remaining_tags = dd_tags[:10], dd_tags[10:] + log.warning( + "To preserve metrics cardinality, only the following first 10 tags have been processed %s. " + "The following tags were not ingested: %s", + dd_tags, + remaining_tags, + ) + return ",".join(dd_tags) + + +def _remap_otel_sdk_config(otel_value: str) -> str: + """Remaps the otel sdk config to ddtrace sdk config""" + if otel_value == "false": + return "True" + elif otel_value == "true": + return "False" + else: + log.warning("OTEL_SDK_DISABLED='%s' is not supported", otel_value) + return otel_value + + +def _remap_default(otel_value: str) -> str: + """Remaps the otel default value to ddtrace default value""" + return otel_value + + +ENV_VAR_MAPPINGS = { + "OTEL_SERVICE_NAME": ("DD_SERVICE", _remap_default), + "OTEL_LOG_LEVEL": ("DD_TRACE_DEBUG", _remap_otel_log_level), + "OTEL_PROPAGATORS": ("DD_TRACE_PROPAGATION_STYLE", _remap_otel_propagators), + "OTEL_TRACES_SAMPLER": ("DD_TRACE_SAMPLE_RATE", _remap_traces_sampler), + "OTEL_TRACES_EXPORTER": ("DD_TRACE_ENABLED", _remap_traces_exporter), + "OTEL_METRICS_EXPORTER": ("DD_RUNTIME_METRICS_ENABLED", _remap_metrics_exporter), + "OTEL_LOGS_EXPORTER": ("", _validate_logs_exporter), # Does not set a DDTRACE environment variable. + "OTEL_RESOURCE_ATTRIBUTES": ("DD_TAGS", _remap_otel_tags), + "OTEL_SDK_DISABLED": ("DD_TRACE_OTEL_ENABLED", _remap_otel_sdk_config), +} + + +def otel_remapping(): + """Checks for the existence of both OTEL and Datadog tracer environment variables and remaps them accordingly. + Datadog Environment variables take precedence over OTEL, but if there isn't a Datadog value present, + then OTEL values take their place. + """ + user_envs = {key.upper(): value for key, value in os.environ.items()} + + for otel_env, otel_value in user_envs.items(): + if otel_env not in ENV_VAR_MAPPINGS: + continue + + dd_env, otel_config_validator = ENV_VAR_MAPPINGS[otel_env] + if dd_env in user_envs: + log.debug( + "Datadog configuration %s is already set. OpenTelemetry configuration will be ignored: %s=%s", + dd_env, + otel_env, + otel_value, + ) + continue + + if otel_env not in ("OTEL_RESOURCE_ATTRIBUTES", "OTEL_SERVICE_NAME"): + # Resource attributes and service name are case-insensitive + otel_value = otel_value.lower() + + mapped_value = otel_config_validator(otel_value) + if mapped_value: + os.environ[dd_env] = mapped_value + log.debug( + "OpenTelemetry configuration %s has been remapped to ddtrace configuration %s=%s", + otel_env, + dd_env, + mapped_value, + ) diff --git a/ddtrace/settings/config.py b/ddtrace/settings/config.py index 47fe9dbeef6..af7bb13de84 100644 --- a/ddtrace/settings/config.py +++ b/ddtrace/settings/config.py @@ -34,6 +34,7 @@ from ..internal.utils.formats import asbool from ..internal.utils.formats import parse_tags_str from ..pin import Pin +from ._otel_remapper import otel_remapping as _otel_remapping from .http import HttpConfig from .integration import IntegrationConfig @@ -375,10 +376,12 @@ def is_error_code(self, status_code): return False def __init__(self): + # Must map Otel configurations to Datadog configurations before creating the config object. + _otel_remapping() # Must come before _integration_configs due to __setattr__ self._config = _default_config() - # use a dict as underlying storing mechanism for integration configs + # Use a dict as underlying storing mechanism for integration configs self._integration_configs = {} self._debug_mode = asbool(os.getenv("DD_TRACE_DEBUG", default=False)) diff --git a/releasenotes/notes/otel-env-var-support-for-dd-trace-py-d4378445a1405bab.yaml b/releasenotes/notes/otel-env-var-support-for-dd-trace-py-d4378445a1405bab.yaml new file mode 100644 index 00000000000..79511c30d39 --- /dev/null +++ b/releasenotes/notes/otel-env-var-support-for-dd-trace-py-d4378445a1405bab.yaml @@ -0,0 +1,15 @@ +--- +features: + - | + tracing: Ensures the following OpenTelemetry environment variables are mapped to an equivalent Datadog configuration (datadog environment variables taking precedence in cases where both are configured):: + + OTEL_SERVICE_NAME -> DD_SERVICE + OTEL_LOG_LEVEL -> DD_TRACE_DEBUG + OTEL_PROPAGATORS -> DD_TRACE_PROPAGATION_STYLE + OTEL_TRACES_SAMPLER -> DD_TRACE_SAMPLE_RATE + OTEL_TRACES_EXPORTER -> DD_TRACE_ENABLED + OTEL_METRICS_EXPORTER -> DD_RUNTIME_METRICS_ENABLED + OTEL_LOGS_EXPORTER -> none + OTEL_RESOURCE_ATTRIBUTES -> DD_TAGS + OTEL_SDK_DISABLED -> DD_TRACE_OTEL_ENABLED + diff --git a/tests/opentelemetry/test_config.py b/tests/opentelemetry/test_config.py new file mode 100644 index 00000000000..ec4ba3a35a9 --- /dev/null +++ b/tests/opentelemetry/test_config.py @@ -0,0 +1,313 @@ +import pytest + + +@pytest.mark.subprocess( + env={ + "OTEL_SERVICE_NAME": "Test", + "DD_SERVICE": "DD_service_test", + "OTEL_LOG_LEVEL": "debug", + "DD_TRACE_DEBUG": "False", + "OTEL_PROPAGATORS": "jaegar, tracecontext, b3", + "DD_TRACE_PROPAGATION_STYLE": "b3", + "OTEL_TRACES_SAMPLER": "always_off", + "DD_TRACE_SAMPLE_RATE": "1.0", + "OTEL_TRACES_EXPORTER": "True", + "DD_TRACE_ENABLED": "True", + "OTEL_METRICS_EXPORTER": "none", + "DD_RUNTIME_METRICS_ENABLED": "True", + "OTEL_LOGS_EXPORTER": "warning", + "OTEL_RESOURCE_ATTRIBUTES": "deployment.environment=prod,service.name=bleh," + "service.version=1.0,testtag1=random1,testtag2=random2,testtag3=random3,testtag4=random4", + "DD_TAGS": "env:staging", + "OTEL_SDK_DISABLED": "True", + "DD_TRACE_OTEL_ENABLED": "True", + }, + err=b"Unsupported OTEL logs exporter value detected: warning. Only the 'none' value is supported.\n", +) +def test_dd_otel_mixed_env_configuration(): + from ddtrace import config + + assert config.service == "DD_service_test", config.service + assert config._debug_mode is False, config._debug_mode + assert config._propagation_style_extract == ["b3"], config._propagation_style_extract + assert config._trace_sample_rate == 1.0, config._trace_sample_rate + assert config._tracing_enabled is True, config._tracing_enabled + assert config._runtime_metrics_enabled is True, config._runtime_metrics_enabled + assert config._otel_enabled is True, config._otel_enabled + assert config.tags == { + "env": "staging", + }, config.tags + + +@pytest.mark.subprocess( + env={ + "OTEL_SERVICE_NAME": "Test", + "OTEL_LOG_LEVEL": "debug", + "OTEL_PROPAGATORS": "jaegar, tracecontext, b3", + "OTEL_TRACES_SAMPLER": "always_off", + "DD_TRACE_SAMPLE_RATE": "1.0", + "OTEL_TRACES_EXPORTER": "OTLP", + "OTEL_METRICS_EXPORTER": "none", + "OTEL_LOGS_EXPORTER": "warning", + "OTEL_RESOURCE_ATTRIBUTES": "deployment.environment=prod,service.name=bleh," + "service.version=1.0,testtag1=random1,testtag2=random2,testtag3=random3,testtag4=random4", + "OTEL_SDK_DISABLED": "False", + }, + err=b"Following style not supported by ddtrace: jaegar.\n" + b"A trace exporter value 'otlp' is set, but not supported. Traces will be exported to Datadog.\n" + b"Unsupported OTEL logs exporter value detected: warning. Only the 'none' value is supported.\n", +) +def test_dd_otel_missing_dd_env_configuration(): + from ddtrace import config + + assert config.service == "Test", config.service + assert config.version == "1.0" + assert config._otel_enabled is True, config._otel_enabled + assert config._debug_mode is True, config._debug_mode + assert config._propagation_style_extract == ["tracecontext", "b3"], config._propagation_style_extract + assert config._trace_sample_rate == 1.0, config._trace_sample_rate + assert config._tracing_enabled is True, config._tracing_enabled + assert config._runtime_metrics_enabled is False, config._runtime_metrics_enabled + assert config.tags == { + "env": "prod", + "testtag1": "random1", + "testtag2": "random2", + "testtag3": "random3", + "testtag4": "random4", + }, config.tags + + +@pytest.mark.subprocess(env={"OTEL_SERVICE_NAME": "Test"}) +def test_otel_service_configuration(): + from ddtrace import config + + assert config.service == "Test", config.service + + +@pytest.mark.subprocess(env={"OTEL_LOG_LEVEL": "debug"}) +def test_otel_log_level_configuration_debug(): + from ddtrace import config + + assert config._debug_mode is True, config._debug_mode + + +@pytest.mark.subprocess( + env={"OTEL_LOG_LEVEL": "trace"}, + err=b"ddtrace does not support otel log level 'trace'. ddtrace only supports enabling debug logs.\n", +) +def test_otel_log_level_configuration_info(): + from ddtrace import config + + assert config._debug_mode is False, config._debug_mode + + +@pytest.mark.subprocess( + env={"OTEL_LOG_LEVEL": "warning"}, + err=b"ddtrace does not support otel log level 'warning'. ddtrace only supports enabling debug logs.\n", +) +def test_otel_log_level_configuration_unsupported(): + from ddtrace import config + + assert config._debug_mode is False, config._debug_mode + + +@pytest.mark.subprocess(env={"OTEL_PROPAGATORS": "b3, tracecontext"}) +def test_otel_propagation_style_configuration(): + from ddtrace import config + + assert config._propagation_style_extract == ["b3", "tracecontext"], config._propagation_style_extract + + +@pytest.mark.subprocess( + env={"OTEL_PROPAGATORS": "jaegar, tracecontext, b3"}, err=b"Following style not supported by ddtrace: jaegar.\n" +) +def test_otel_propagation_style_configuration_unsupportedwarning(): + from ddtrace import config + + assert config._propagation_style_extract == ["tracecontext", "b3"], config._propagation_style_extract + + +@pytest.mark.subprocess( + env={"OTEL_TRACES_SAMPLER": "always_on"}, + err=b"Trace sampler set from always_on to parentbased_always_on; only parent based sampling is supported.\n", +) +def test_otel_traces_sampler_configuration_alwayson(): + from ddtrace import config + + assert config._trace_sample_rate == 1.0, config._trace_sample_rate + + +@pytest.mark.subprocess( + env={"OTEL_TRACES_SAMPLER": "always_on"}, + err=b"Trace sampler set from always_on to parentbased_always_on; only parent based sampling is supported.\n", +) +def test_otel_traces_sampler_configuration_ignore_parent(): + from ddtrace import config + + assert config._trace_sample_rate == 1.0, config._trace_sample_rate + + +@pytest.mark.subprocess( + env={"OTEL_TRACES_SAMPLER": "always_off"}, + err=b"Trace sampler set from always_off to parentbased_always_off; only parent based sampling is supported.\n", +) +def test_otel_traces_sampler_configuration_alwaysoff(): + from ddtrace import config + + assert config._trace_sample_rate == 0.0, config._trace_sample_rate + + +@pytest.mark.subprocess( + env={ + "OTEL_TRACES_SAMPLER": "traceidratio", + "OTEL_TRACES_SAMPLER_ARG": "0.5", + }, + err=b"Trace sampler set from traceidratio to parentbased_traceidratio; only parent based sampling is supported.\n", +) +def test_otel_traces_sampler_configuration_traceidratio(): + from ddtrace import config + + assert config._trace_sample_rate == 0.5, config._trace_sample_rate + + +@pytest.mark.subprocess(env={"OTEL_TRACES_EXPORTER": "none"}) +def test_otel_traces_exporter_configuration(): + from ddtrace import config + + assert config._tracing_enabled is False, config._tracing_enabled + + +@pytest.mark.subprocess( + env={"OTEL_TRACES_EXPORTER": "true"}, + err=b"A trace exporter value 'true' is set, but not supported. Traces will be exported to Datadog.\n", +) +def test_otel_traces_exporter_configuration_unsupported_exporter(): + from ddtrace import config + + assert config._tracing_enabled is True, config._tracing_enabled + + +@pytest.mark.subprocess(env={"OTEL_METRICS_EXPORTER": "none"}) +def test_otel_metrics_exporter_configuration(): + from ddtrace import config + + assert config._runtime_metrics_enabled is False, config._runtime_metrics_enabled + + +@pytest.mark.subprocess( + env={"OTEL_METRICS_EXPORTER": "true"}, + err=b"Metrics exporter value is set to unrecognized value: true.\n", +) +def test_otel_metrics_exporter_configuration_unsupported_exporter(): + from ddtrace import config + + assert config._runtime_metrics_enabled is False, config._runtime_metrics_enabled + + +@pytest.mark.subprocess( + env={"otel_LOGS_EXPORTER": "console"}, + err=b"Unsupported OTEL logs exporter value detected: console. Only the 'none' value is supported.\n", +) +def test_otel_logs_exporter_configuration_unsupported(): + from ddtrace import config # noqa: F401 + + +@pytest.mark.subprocess(env={"OTEL_LOGS_EXPORTER": "none"}, err=b"") +def test_otel_logs_exporter_configuration(): + """ + Testing that a warning is not logged when 'none' value is found. + """ + from ddtrace import config # noqa: F401 + + +@pytest.mark.subprocess( + env={"OTEL_RESOURCE_ATTRIBUTES": "deployment.environment=prod,service.name=bleh,service.version=1.0"} +) +def test_otel_resource_attributes_unified_tags(): + from ddtrace import config + + assert config.service == "bleh" + assert config.version == "1.0" + assert config.env == "prod" + + +@pytest.mark.subprocess( + env={"OTEL_RESOURCE_ATTRIBUTES": "deployment.environment:prod,service.name:bleh,service.version:1.0"}, + err=b"DDTRACE failed to read OTEL_RESOURCE_ATTRIBUTES. This value is misformatted: " + b"deployment.environment:prod,service.name:bleh,service.version:1.0\n", +) +def test_otel_resource_attributes_misconfigured_tags(): + from ddtrace import config # noqa: F401 + + +@pytest.mark.subprocess( + env={ + "OTEL_RESOURCE_ATTRIBUTES": "deployment.environment=prod,service.name=bleh," + "service.version=1.0,testtag1=random1,testtag2=random2,testtag3=random3,testtag4=random4" + } +) +def test_otel_resource_attributes_mixed_tags(): + from ddtrace import config + + assert config.service == "bleh" + assert config.version == "1.0" + assert config.env == "prod" + assert config.tags == { + "env": "prod", + "testtag1": "random1", + "testtag2": "random2", + "testtag3": "random3", + "testtag4": "random4", + }, config.tags + + +@pytest.mark.subprocess( + env={ + "OTEL_RESOURCE_ATTRIBUTES": "deployment.environment=prod,service.name=bleh," + "service.version=1.0,testtag1=random1,testtag2=random2,testtag3=random3,testtag4=random4,testtag5=random5," + "testtag6=random6,testtag7=random7,testtag8=random8" + }, + err=b"To preserve metrics cardinality, only the following first 10 tags have been processed " + b"['version:1.0', 'service:bleh', 'env:prod', 'testtag1:random1', 'testtag2:random2', 'testtag3:random3', " + b"'testtag4:random4', 'testtag5:random5', 'testtag6:random6', 'testtag7:random7']. " + b"The following tags were not ingested: ['testtag8:random8']\n", +) +def test_otel_resource_attributes_tags_warning(): + from ddtrace import config + + assert config.env == "prod" + assert config.service == "bleh", config.service + assert config.version == "1.0" + assert config.tags == { + "env": "prod", + "testtag1": "random1", + "testtag2": "random2", + "testtag3": "random3", + "testtag4": "random4", + "testtag5": "random5", + "testtag6": "random6", + "testtag7": "random7", + }, config.tags + + +@pytest.mark.subprocess(env={"OTEL_SDK_DISABLED": "false"}) +def test_otel_sdk_disabled_configuration(): + from ddtrace import config + + assert config._otel_enabled is True + + +@pytest.mark.subprocess(env={"OTEL_SDK_DISABLED": "true"}) +def test_otel_sdk_disabled_configuration_true(): + from ddtrace import config + + assert config._otel_enabled is False, config._otel_enabled + + +@pytest.mark.subprocess( + env={"OTEL_RESOURCE_ATTRIBUTES": "service.version=1.0"}, +) +def test_otel_resource_attributes_version_tag(): + from ddtrace import config + + assert config.version == "1.0" From 9e3bd1fb9e42a4aa143cae661547517c7fbd8924 Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Thu, 23 May 2024 09:51:32 +0200 Subject: [PATCH 101/104] chore(iast): add local debug scripts to find leaks (#9318) Add to the repository the scripts that @juanjux and I use to debug C++ leaks. This folder (scripts/iast/) contains some scripts to check the memory usage of native code. ### 1. Build the docker image ```sh docker build . -f docker/Dockerfile_py311_debug_mode -t python_311_debug ``` ### 2. Run the docker container #### 2.1. Run the container with the script to find references (this script will run the memory usage check) ```sh docker run --rm -it -v ${PWD}:/ddtrace python_311_debug /bin/bash -c "cd /ddtrace && scripts/iast/run_references.sh" >> References: 1003 >> References: 2 >> References: 2 >> References: 2 >> References: 2 >> References: 2 ``` #### 2.2. Run the container with the script with memray usage check ```sh docker run --rm -it -v ${PWD}:/ddtrace python_311_debug /bin/bash -c "cd /ddtrace && scripts/iast/run_memray.sh" google-chrome file://$PWD/memray-flamegraph-lel.html ``` #### 2.3. Run the container with the script with Max RSS ```sh docker run --rm -it -v ${PWD}:/ddtrace python_311_debug /bin/bash -c "cd /ddtrace && scripts/iast/run_memory.sh" >> Round 0 Max RSS: 41.9453125 >> 42.2109375 ``` ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. - [x] If change touches code that signs or publishes builds or packages, or handles credentials of any kind, I've requested a review from `@DataDog/security-design-and-guidance`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Co-authored-by: erikayasuda <153395705+erikayasuda@users.noreply.github.com> --- docker/Dockerfile_py311_debug_mode | 91 + docker/Dockerfile_py312_debug_mode | 91 + scripts/iast/.env | 12 + scripts/iast/README | 77 + scripts/iast/mod_leak_functions.py | 76 + scripts/iast/requirements.txt | 1 + scripts/iast/run_memory.sh | 3 + scripts/iast/run_memray.sh | 4 + scripts/iast/run_references.sh | 4 + scripts/iast/test_leak_functions.py | 35 + scripts/iast/test_references.py | 28 + scripts/iast/valgrind-python.supp | 24231 ++++++++++++++++++++++++++ 12 files changed, 24653 insertions(+) create mode 100644 docker/Dockerfile_py311_debug_mode create mode 100644 docker/Dockerfile_py312_debug_mode create mode 100644 scripts/iast/.env create mode 100644 scripts/iast/README create mode 100644 scripts/iast/mod_leak_functions.py create mode 100644 scripts/iast/requirements.txt create mode 100644 scripts/iast/run_memory.sh create mode 100644 scripts/iast/run_memray.sh create mode 100644 scripts/iast/run_references.sh create mode 100644 scripts/iast/test_leak_functions.py create mode 100644 scripts/iast/test_references.py create mode 100644 scripts/iast/valgrind-python.supp diff --git a/docker/Dockerfile_py311_debug_mode b/docker/Dockerfile_py311_debug_mode new file mode 100644 index 00000000000..5a7e131e20e --- /dev/null +++ b/docker/Dockerfile_py311_debug_mode @@ -0,0 +1,91 @@ +# DEV: Use `debian:slim` instead of an `alpine` image to support installing wheels from PyPI +# this drastically improves test execution time since python dependencies don't all +# have to be built from source all the time (grpcio takes forever to install) +FROM debian:buster-20221219-slim + +# http://bugs.python.org/issue19846 +# > At the moment, setting "LANG=C" on a Linux system *fundamentally breaks Python 3*, and that's not OK. +ENV LANG C.UTF-8 + +# https://support.circleci.com/hc/en-us/articles/360045268074-Build-Fails-with-Too-long-with-no-output-exceeded-10m0s-context-deadline-exceeded- +ENV PYTHONUNBUFFERED=1 +# Configure PATH environment for pyenv +ENV PYTHON_SOURCE=/root/python_source +ENV PYTHON_DEBUG=/root/env/python_debug +ENV PATH=$PATH:${PYTHON_DEBUG}/bin +ENV PYTHON_CONFIGURE_OPTS=--enable-shared + +RUN \ + # Install system dependencies + apt-get update \ + && apt-get install -y --no-install-recommends \ + apt-transport-https \ + build-essential \ + ca-certificates \ + clang-format \ + curl \ + git \ + gnupg \ + jq \ + libbz2-dev \ + libenchant-dev \ + libffi-dev \ + liblzma-dev \ + libmemcached-dev \ + libncurses5-dev \ + libncursesw5-dev \ + libpq-dev \ + libreadline-dev \ + libsasl2-dev \ + libsqlite3-dev \ + libsqliteodbc \ + libssh-dev \ + libssl-dev \ + patch \ + python-openssl\ + unixodbc-dev \ + wget \ + zlib1g-dev \ + valgrind \ + # Cleaning up apt cache space + && rm -rf /var/lib/apt/lists/* + +# Install pyenv and necessary Python versions +# `--with-pydebug`: [Add options](https://docs.python.org/3/using/configure.html#python-debug-build) like count references, sanity checks... +# `--with-valgrind`: Enable Valgrind support (default is no). +# `--without-pymalloc`: Python has a pymalloc allocator optimized for small objects (smaller or equal to 512 bytes) with a short lifetime. We remove this functionality to not hide errors +RUN git clone --depth 1 --branch v3.11.6 https://github.com/python/cpython/ "${PYTHON_SOURCE}" \ + && cd ${PYTHON_SOURCE} \ + && ./configure --with-pydebug --without-pymalloc --with-valgrind --prefix ${PYTHON_DEBUG} \ + && make OPT=-g \ + && make install \ + && cd - + +RUN python3.11 -m pip install -U pip \ + && python3.11 -m pip install six cattrs setuptools cython wheel cmake pytest pytest-cov hypothesis pytest-memray\ + memray==1.12.0 \ + requests==2.31.0 \ + attrs>=20 \ + bytecode>=0.14.0 \ + cattrs \ + ddsketch>=3.0.0 \ + envier~=0.5 \ + opentelemetry-api>=1 \ + protobuf>=3 \ + six>=1.12.0 \ + typing_extensions \ + xmltodict>=0.12 + + +CMD ["/bin/bash"] +#docker build . -f docker/Dockerfile_py311_debug_mode -t python_311_debug +#docker run --rm -it -v ${PWD}:/ddtrace python_311_debug +# +# Now, you can check IAST leaks: +#cd /ddtrace +#export PATH=$PATH:$PWD +#export PYTHONPATH=$PYTHONPATH:$PWD +#export PYTHONMALLOC=malloc +#python3.11 ddtrace/appsec/_iast/leak.py +#python3.11 -m memray run --trace-python-allocators --native -o lel.bin -f prueba.py +#python3.11 -m memray flamegraph lel.bin --leaks -f \ No newline at end of file diff --git a/docker/Dockerfile_py312_debug_mode b/docker/Dockerfile_py312_debug_mode new file mode 100644 index 00000000000..28c1fb937b0 --- /dev/null +++ b/docker/Dockerfile_py312_debug_mode @@ -0,0 +1,91 @@ +# DEV: Use `debian:slim` instead of an `alpine` image to support installing wheels from PyPI +# this drastically improves test execution time since python dependencies don't all +# have to be built from source all the time (grpcio takes forever to install) +FROM debian:buster-20221219-slim + +# http://bugs.python.org/issue19846 +# > At the moment, setting "LANG=C" on a Linux system *fundamentally breaks Python 3*, and that's not OK. +ENV LANG C.UTF-8 + +# https://support.circleci.com/hc/en-us/articles/360045268074-Build-Fails-with-Too-long-with-no-output-exceeded-10m0s-context-deadline-exceeded- +ENV PYTHONUNBUFFERED=1 +# Configure PATH environment for pyenv +ENV PYTHON_SOURCE=/root/python_source +ENV PYTHON_DEBUG=/root/env/python_debug +ENV PATH=$PATH:${PYTHON_DEBUG}/bin +ENV PYTHON_CONFIGURE_OPTS=--enable-shared + +RUN \ + # Install system dependencies + apt-get update \ + && apt-get install -y --no-install-recommends \ + apt-transport-https \ + build-essential \ + ca-certificates \ + clang-format \ + curl \ + git \ + gnupg \ + jq \ + libbz2-dev \ + libenchant-dev \ + libffi-dev \ + liblzma-dev \ + libmemcached-dev \ + libncurses5-dev \ + libncursesw5-dev \ + libpq-dev \ + libreadline-dev \ + libsasl2-dev \ + libsqlite3-dev \ + libsqliteodbc \ + libssh-dev \ + libssl-dev \ + patch \ + python-openssl\ + unixodbc-dev \ + wget \ + zlib1g-dev \ + valgrind \ + # Cleaning up apt cache space + && rm -rf /var/lib/apt/lists/* + +# Install pyenv and necessary Python versions +# `--with-pydebug`: [Add options](https://docs.python.org/3/using/configure.html#python-debug-build) like count references, sanity checks... +# `--with-valgrind`: Enable Valgrind support (default is no). +# `--without-pymalloc`: Python has a pymalloc allocator optimized for small objects (smaller or equal to 512 bytes) with a short lifetime. We remove this functionality to not hide errors +RUN git clone --depth 1 --branch v3.12.3 https://github.com/python/cpython/ "${PYTHON_SOURCE}" \ + && cd ${PYTHON_SOURCE} \ + && ./configure --with-pydebug --without-pymalloc --with-valgrind --prefix ${PYTHON_DEBUG} \ + && make OPT=-g \ + && make install \ + && cd - + +RUN python3.12 -m pip install -U pip \ + && python3.12 -m pip install six cattrs setuptools cython wheel cmake pytest pytest-cov hypothesis pytest-memray\ + memray==1.12.0 \ + requests==2.31.0 \ + attrs>=20 \ + bytecode>=0.14.0 \ + cattrs \ + ddsketch>=3.0.0 \ + envier~=0.5 \ + opentelemetry-api>=1 \ + protobuf>=3 \ + six>=1.12.0 \ + typing_extensions \ + xmltodict>=0.12 + + +CMD ["/bin/bash"] +#docker build . -f docker/Dockerfile_py311_debug_mode -t python_311_debug +#docker run --rm -it -v ${PWD}:/ddtrace python_311_debug +# +# Now, you can check IAST leaks: +#cd /ddtrace +#export PATH=$PATH:$PWD +#export PYTHONPATH=$PYTHONPATH:$PWD +#export PYTHONMALLOC=malloc +#python3.12 ddtrace/appsec/_iast/leak.py +#python3.12 -m memray run --trace-python-allocators --native -o lel.bin -f prueba.py +#python3.12 -m memray flamegraph lel.bin --leaks -f \ No newline at end of file diff --git a/scripts/iast/.env b/scripts/iast/.env new file mode 100644 index 00000000000..9f4b5910837 --- /dev/null +++ b/scripts/iast/.env @@ -0,0 +1,12 @@ +export PATH=$PATH:$PWD +export PYTHONPATH=$PYTHONPATH:$PWD +export PYTHON_VERSION=python3.11 +export PYTHONMALLOC=malloc +export DD_COMPILE_DEBUG=true +export DD_TRACE_ENABLED=true +export DD_IAST_ENABLED=true +export _DD_IAST_DEBUG=true +export DD_IAST_REQUEST_SAMPLING=100 +export _DD_APPSEC_DEDUPLICATION_ENABLED=false +export DD_INSTRUMENTATION_TELEMETRY_ENABLED=true +export DD_REMOTE_CONFIGURATION_ENABLED=false \ No newline at end of file diff --git a/scripts/iast/README b/scripts/iast/README new file mode 100644 index 00000000000..f2f2a0af840 --- /dev/null +++ b/scripts/iast/README @@ -0,0 +1,77 @@ +This folder (scripts/iast/) contains some scripts to check memory usage of native code. + +## How to use + +### 1. Build the docker image + +```sh +docker build . -f docker/Dockerfile_py311_debug_mode -t python_311_debug +``` + +### 2. Run the docker container + +#### 2.1. Run the container with the script to find references (this script will run the memory usage check) + +```sh +docker run --rm -it -v ${PWD}:/ddtrace python_311_debug /bin/bash -c "cd /ddtrace && source scripts/iast/.env && \ +sh scripts/iast/run_references.sh" +>> References: 1003 +>> References: 2 +>> References: 2 +>> References: 2 +>> References: 2 +>> References: 2 +``` + +#### 2.2. Run the container with the script with memray usage check + +```sh +docker run --rm -it -v ${PWD}:/ddtrace python_311_debug /bin/bash -c "cd /ddtrace && source scripts/iast/.env && \ +sh scripts/iast/run_memray.sh" +google-chrome file://$PWD/memray-flamegraph-lel.html +``` + +#### 2.3. Run the container with the script with Max RSS + +```sh +docker run --rm -it -v ${PWD}:/ddtrace python_311_debug /bin/bash -c "cd /ddtrace && source scripts/iast/.env && \ +sh scripts/iast/run_memory.sh" +>> Round 0 Max RSS: 41.9453125 +>> 42.2109375 +``` + +#### 2.4. Run the container with valgrind + +- `--tool`: default: memcheck, other options: cachegrind, callgrind, helgrind, drd, massif, dhat, lackey, none, exp-bbv + - memcheck: + - `--leak-check`: options summary/full/yes + - massif: heap profiler, see below +- `--track-origins`: increases the size of the basic block translations +- `--suppressions`: path to our suppression file: `scripts/iast/valgrind-python.supp` +- `--log-file`: Valgrind report a lot information, we store this info in a file to analyze carefully the reports + +docker run --rm -it -v ${PWD}:/ddtrace python_311_debug /bin/bash -c "cd /ddtrace && source scripts/iast/.env && \ +valgrind --tool=memcheck --leak-check=full --log-file=scripts/iast/valgrind_bench_overload.out --track-origins=yes \ +--suppressions=scripts/iast/valgrind-python.supp --show-leak-kinds=all \ +python3.11 scripts/iast/test_leak_functions.py 100" + +##### Understanding results of memcheck + +Valgrind Memcheck returns all traces of C and C++ files. Most of them are Python core traces. These traces could be +memory leaks in our Python code, but we can't interpret them at the moment. Therefore, all of them are in the + suppression file. + + +The valid traces of our C files, are like that: +``` +==324555== 336 bytes in 1 blocks are possibly lost in loss record 4,806 of 5,852 +==324555== at 0x483DD99: calloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) +==324555== by 0x40149CA: allocate_dtv (dl-tls.c:286) +==324555== by 0x40149CA: _dl_allocate_tls (dl-tls.c:532) +==324555== by 0x486E322: allocate_stack (allocatestack.c:622) +==324555== by 0x486E322: pthread_create@@GLIBC_2.2.5 (pthread_create.c:660) +==324555== by 0xFBF078E: ??? (in /root/ddtrace/native-core.so) +==324555== by 0x19D312C7: ??? +==324555== by 0x1FFEFEFAFF: ??? +==324555== +``` diff --git a/scripts/iast/mod_leak_functions.py b/scripts/iast/mod_leak_functions.py new file mode 100644 index 00000000000..d662606929a --- /dev/null +++ b/scripts/iast/mod_leak_functions.py @@ -0,0 +1,76 @@ +import os +import random +import subprocess + +import requests + +from ddtrace.appsec._iast._utils import _is_iast_enabled + + +if _is_iast_enabled(): + from ddtrace.appsec._iast._taint_tracking import OriginType + from ddtrace.appsec._iast._taint_tracking import taint_pyobject + + +def test_doit(): + origin_string1 = "hiroot" + + if _is_iast_enabled(): + tainted_string_2 = taint_pyobject( + pyobject="1234", source_name="abcdefghijk", source_value="1234", source_origin=OriginType.PARAMETER + ) + else: + tainted_string_2 = "1234" + + string1 = str(origin_string1) # String with 1 propagation range + string2 = str(tainted_string_2) # String with 1 propagation range + + string3 = string1 + string2 # 2 propagation ranges: hiroot1234 + string4 = "-".join([string3, string3, string3]) # 6 propagation ranges: hiroot1234-hiroot1234-hiroot1234 + string5 = string4[0:20] # 1 propagation range: hiroot1234-hiroot123 + string6 = string5.title() # 1 propagation range: Hiroot1234-Hiroot123 + string7 = string6.upper() # 1 propagation range: HIROOT1234-HIROOT123 + string8 = "%s_notainted" % string7 # 1 propagation range: HIROOT1234-HIROOT123_notainted + string9 = "notainted_{}".format(string8) # 1 propagation range: notainted_HIROOT1234-HIROOT123_notainted + string10 = "nottainted\n" + string9 # 2 propagation ranges: notainted\nnotainted_HIROOT1234-HIROOT123_notainted + string11 = string10.splitlines()[1] # 1 propagation range: notainted_HIROOT1234-HIROOT123_notainted + string12 = string11 + "_notainted" # 1 propagation range: notainted_HIROOT1234-HIROOT123_notainted_notainted + string13 = string12.rsplit("_", 1)[0] # 1 propagation range: notainted_HIROOT1234-HIROOT123_notainted + + try: + # Path traversal vulnerability + m = open("/" + string13 + ".txt") + _ = m.read() + except Exception: + pass + + try: + # Command Injection vulnerability + _ = subprocess.Popen("ls " + string9) + except Exception: + pass + + try: + # SSRF vulnerability + requests.get("http://" + "foobar") + # urllib3.request("GET", "http://" + "foobar") + except Exception: + pass + + # Weak Randomness vulnerability + _ = random.randint(1, 10) + + # os path propagation + string14 = os.path.join(string13, "a") # 1 propagation range: notainted_HIROOT1234-HIROOT123_notainted/a + string15 = os.path.split(string14)[0] # 1 propagation range: notainted_HIROOT1234-HIROOT123_notainted + string16 = os.path.dirname( + string15 + "/" + "foobar" + ) # 1 propagation range: notainted_HIROOT1234-HIROOT123_notainted + string17 = os.path.basename("/foobar/" + string16) # 1 propagation range: notainted_HIROOT1234-HIROOT123_notainted + string18 = os.path.splitext(string17 + ".jpg")[0] # 1 propagation range: notainted_HIROOT1234-HIROOT123_notainted + string19 = os.path.normcase(string18) # 1 propagation range: notainted_HIROOT1234-HIROOT123_notainted + string20 = os.path.splitdrive(string19)[1] # 1 propagation range: notainted_HIROOT1234-HIROOT123_notainted + + expected = "notainted_HIROOT1234-HIROOT123_notainted" # noqa: F841 + # assert string20 == expected + return string20 diff --git a/scripts/iast/requirements.txt b/scripts/iast/requirements.txt new file mode 100644 index 00000000000..0fd2996a90d --- /dev/null +++ b/scripts/iast/requirements.txt @@ -0,0 +1 @@ +memray==1.12.0 \ No newline at end of file diff --git a/scripts/iast/run_memory.sh b/scripts/iast/run_memory.sh new file mode 100644 index 00000000000..50c2822d668 --- /dev/null +++ b/scripts/iast/run_memory.sh @@ -0,0 +1,3 @@ +PYTHON="${PYTHON_VERSION:-python3.11}" +$PYTHON -m pip install -r scripts/iast/requirements.txt +$PYTHON scripts/iast/test_leak_functions.py 1000000 \ No newline at end of file diff --git a/scripts/iast/run_memray.sh b/scripts/iast/run_memray.sh new file mode 100644 index 00000000000..65d5eb5e49c --- /dev/null +++ b/scripts/iast/run_memray.sh @@ -0,0 +1,4 @@ +PYTHON="${PYTHON_VERSION:-python3.11}" +$PYTHON -m pip install -r scripts/iast/requirements.txt +$PYTHON -m memray run --trace-python-allocators --aggregate --native -o lel.bin -f scripts/iast/test_leak_functions.py 100 +$PYTHON -m memray flamegraph lel.bin --leaks -f \ No newline at end of file diff --git a/scripts/iast/run_references.sh b/scripts/iast/run_references.sh new file mode 100644 index 00000000000..9d3357fb319 --- /dev/null +++ b/scripts/iast/run_references.sh @@ -0,0 +1,4 @@ +PYTHON="${PYTHON_VERSION:-python3.11}" +# $PYTHON setup.py build_ext --inplace +${PYTHON} -m pip install -r scripts/iast/requirements.txt +${PYTHON} -m ddtrace.commands.ddtrace_run ${PYTHON} scripts/iast/test_references.py \ No newline at end of file diff --git a/scripts/iast/test_leak_functions.py b/scripts/iast/test_leak_functions.py new file mode 100644 index 00000000000..6e276520367 --- /dev/null +++ b/scripts/iast/test_leak_functions.py @@ -0,0 +1,35 @@ +import ddtrace.auto # noqa: F401 # isort: skip +import resource +import sys + +from mod_leak_functions import test_doit + +from ddtrace.appsec._iast._taint_tracking import create_context +from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted +from ddtrace.appsec._iast._taint_tracking import reset_context + + +def test_main(): + try: + rounds = int(sys.argv[1]) + except ValueError: + rounds = 1 + print("Test %d rounds" % rounds) + for i in range(rounds): + try: + create_context() + result = test_doit() # noqa: F841 + assert is_pyobject_tainted(result) + reset_context() + except KeyboardInterrupt: + print("Control-C stopped at %d rounds" % i) + break + if i % 250 == 0: + print("Round %d Max RSS: " % i, end="") + print(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024) + print("Round %d Max RSS: " % rounds, end="") + print(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024) + + +if __name__ == "__main__": + test_main() diff --git a/scripts/iast/test_references.py b/scripts/iast/test_references.py new file mode 100644 index 00000000000..c5c172f7742 --- /dev/null +++ b/scripts/iast/test_references.py @@ -0,0 +1,28 @@ +import gc +import sys + +from mod_leak_functions import test_doit + +from ddtrace.appsec._iast._taint_tracking import create_context +from ddtrace.appsec._iast._taint_tracking import is_pyobject_tainted +from ddtrace.appsec._iast._taint_tracking import reset_context + + +def test_main(): + for i in range(100): + gc.collect() + a = sys.gettotalrefcount() + try: + create_context() + result = test_doit() # noqa: F841 + assert is_pyobject_tainted(result) + reset_context() + except KeyboardInterrupt: + print("Control-C stopped at %d rounds" % i) + break + gc.collect() + print("References: %d " % (sys.gettotalrefcount() - a)) + + +if __name__ == "__main__": + test_main() diff --git a/scripts/iast/valgrind-python.supp b/scripts/iast/valgrind-python.supp new file mode 100644 index 00000000000..8441cc3f8e6 --- /dev/null +++ b/scripts/iast/valgrind-python.supp @@ -0,0 +1,24231 @@ +# +# This is a valgrind suppression file that should be used when using valgrind. +# +# Here's an example of running valgrind: +# +# cd python/dist/src +# valgrind --tool=memcheck --suppressions=Misc/valgrind-python.supp \ +# ./python -E -tt ./Lib/test/regrtest.py -u bsddb,network +# +# You must edit Objects/obmalloc.c and uncomment Py_USING_MEMORY_DEBUGGER +# to use the preferred suppressions with Py_ADDRESS_IN_RANGE. +# +# If you do not want to recompile Python, you can uncomment +# suppressions for PyObject_Free and PyObject_Realloc. +# +# See Misc/README.valgrind for more information. + +# all tool names: Addrcheck,Memcheck,cachegrind,helgrind,massif +{ + ADDRESS_IN_RANGE/Invalid read of size 4 + Memcheck:Addr4 + fun:Py_ADDRESS_IN_RANGE +} + +{ + ADDRESS_IN_RANGE/Invalid read of size 4 + Memcheck:Value4 + fun:Py_ADDRESS_IN_RANGE +} + +{ + ADDRESS_IN_RANGE/Invalid read of size 8 (x86_64 aka amd64) + Memcheck:Value8 + fun:Py_ADDRESS_IN_RANGE +} + +{ + ADDRESS_IN_RANGE/Conditional jump or move depends on uninitialised value + Memcheck:Cond + fun:Py_ADDRESS_IN_RANGE +} + +# +# Leaks (including possible leaks) +# Hmmm, I wonder if this masks some real leaks. I think it does. +# Will need to fix that. +# + +{ + Suppress leaking the GIL. Happens once per process, see comment in ceval.c. + Memcheck:Leak + fun:malloc + fun:PyThread_allocate_lock + fun:PyEval_InitThreads +} + +{ + Suppress leaking the GIL after a fork. + Memcheck:Leak + fun:malloc + fun:PyThread_allocate_lock + fun:PyEval_ReInitThreads +} + +{ + Suppress leaking the autoTLSkey. This looks like it shouldn't leak though. + Memcheck:Leak + fun:malloc + fun:PyThread_create_key + fun:_PyGILState_Init + fun:Py_InitializeEx + fun:Py_Main +} + +{ + Hmmm, is this a real leak or like the GIL? + Memcheck:Leak + fun:malloc + fun:PyThread_ReInitTLS +} + +{ + Handle PyMalloc confusing valgrind (possibly leaked) + Memcheck:Leak + fun:realloc + fun:_PyObject_GC_Resize + fun:COMMENT_THIS_LINE_TO_DISABLE_LEAK_WARNING +} + +{ + Handle PyMalloc confusing valgrind (possibly leaked) + Memcheck:Leak + fun:malloc + fun:_PyObject_GC_New + fun:COMMENT_THIS_LINE_TO_DISABLE_LEAK_WARNING +} + +{ + Handle PyMalloc confusing valgrind (possibly leaked) + Memcheck:Leak + fun:malloc + fun:_PyObject_GC_NewVar + fun:COMMENT_THIS_LINE_TO_DISABLE_LEAK_WARNING +} + +# +# Non-python specific leaks +# + +{ + Handle pthread issue (possibly leaked) + Memcheck:Leak + fun:calloc + fun:allocate_dtv + fun:_dl_allocate_tls_storage + fun:_dl_allocate_tls +} + +{ + Handle pthread issue (possibly leaked) + Memcheck:Leak + fun:memalign + fun:_dl_allocate_tls_storage + fun:_dl_allocate_tls +} + +{ + ADDRESS_IN_RANGE/Invalid read of size 4 + Memcheck:Addr4 + fun:PyObject_Free +} + +{ + ADDRESS_IN_RANGE/Invalid read of size 4 + Memcheck:Value4 + fun:PyObject_Free +} + +{ + ADDRESS_IN_RANGE/Conditional jump or move depends on uninitialised value + Memcheck:Cond + fun:PyObject_Free +} + +{ + ADDRESS_IN_RANGE/Invalid read of size 4 + Memcheck:Addr4 + fun:PyObject_Realloc +} + +{ + ADDRESS_IN_RANGE/Invalid read of size 4 + Memcheck:Value4 + fun:PyObject_Realloc +} + +{ + ADDRESS_IN_RANGE/Conditional jump or move depends on uninitialised value + Memcheck:Cond + fun:PyObject_Realloc +} + +### +### All the suppressions below are for errors that occur within libraries +### that Python uses. The problems to not appear to be related to Python's +### use of the libraries. +### + +{ + Generic ubuntu ld problems + Memcheck:Addr8 + obj:/lib/ld-2.4.so + obj:/lib/ld-2.4.so + obj:/lib/ld-2.4.so + obj:/lib/ld-2.4.so +} + +{ + Generic gentoo ld problems + Memcheck:Cond + obj:/lib/ld-2.3.4.so + obj:/lib/ld-2.3.4.so + obj:/lib/ld-2.3.4.so + obj:/lib/ld-2.3.4.so +} + +{ + DBM problems, see test_dbm + Memcheck:Param + write(buf) + fun:write + obj:/usr/lib/libdb1.so.2 + obj:/usr/lib/libdb1.so.2 + obj:/usr/lib/libdb1.so.2 + obj:/usr/lib/libdb1.so.2 + fun:dbm_close +} + +{ + DBM problems, see test_dbm + Memcheck:Value8 + fun:memmove + obj:/usr/lib/libdb1.so.2 + obj:/usr/lib/libdb1.so.2 + obj:/usr/lib/libdb1.so.2 + obj:/usr/lib/libdb1.so.2 + fun:dbm_store + fun:dbm_ass_sub +} + +{ + DBM problems, see test_dbm + Memcheck:Cond + obj:/usr/lib/libdb1.so.2 + obj:/usr/lib/libdb1.so.2 + obj:/usr/lib/libdb1.so.2 + fun:dbm_store + fun:dbm_ass_sub +} + +{ + DBM problems, see test_dbm + Memcheck:Cond + fun:memmove + obj:/usr/lib/libdb1.so.2 + obj:/usr/lib/libdb1.so.2 + obj:/usr/lib/libdb1.so.2 + obj:/usr/lib/libdb1.so.2 + fun:dbm_store + fun:dbm_ass_sub +} + +{ + GDBM problems, see test_gdbm + Memcheck:Param + write(buf) + fun:write + fun:gdbm_open + +} + +{ + ZLIB problems, see test_gzip + Memcheck:Cond + obj:/lib/libz.so.1.2.3 + obj:/lib/libz.so.1.2.3 + fun:deflate +} + +{ + Avoid problems w/readline doing a putenv and leaking on exit + Memcheck:Leak + fun:malloc + fun:xmalloc + fun:sh_set_lines_and_columns + fun:_rl_get_screen_size + fun:_rl_init_terminal_io + obj:/lib/libreadline.so.4.3 + fun:rl_initialize +} + +### +### These occur from somewhere within the SSL, when running +### test_socket_sll. They are too general to leave on by default. +### +###{ +### somewhere in SSL stuff +### Memcheck:Cond +### fun:memset +###} +###{ +### somewhere in SSL stuff +### Memcheck:Value4 +### fun:memset +###} +### +###{ +### somewhere in SSL stuff +### Memcheck:Cond +### fun:MD5_Update +###} +### +###{ +### somewhere in SSL stuff +### Memcheck:Value4 +### fun:MD5_Update +###} + +# +# All of these problems come from using test_socket_ssl +# +{ + from test_socket_ssl + Memcheck:Cond + fun:BN_bin2bn +} + +{ + from test_socket_ssl + Memcheck:Cond + fun:BN_num_bits_word +} + +{ + from test_socket_ssl + Memcheck:Value4 + fun:BN_num_bits_word +} + +{ + from test_socket_ssl + Memcheck:Cond + fun:BN_mod_exp_mont_word +} + +{ + from test_socket_ssl + Memcheck:Cond + fun:BN_mod_exp_mont +} + +{ + from test_socket_ssl + Memcheck:Param + write(buf) + fun:write + obj:/usr/lib/libcrypto.so.0.9.7 +} + +{ + from test_socket_ssl + Memcheck:Cond + fun:RSA_verify +} + +{ + from test_socket_ssl + Memcheck:Value4 + fun:RSA_verify +} + +{ + from test_socket_ssl + Memcheck:Value4 + fun:DES_set_key_unchecked +} + +{ + from test_socket_ssl + Memcheck:Value4 + fun:DES_encrypt2 +} + +{ + from test_socket_ssl + Memcheck:Cond + obj:/usr/lib/libssl.so.0.9.7 +} + +{ + from test_socket_ssl + Memcheck:Value4 + obj:/usr/lib/libssl.so.0.9.7 +} + +{ + from test_socket_ssl + Memcheck:Cond + fun:BUF_MEM_grow_clean +} + +{ + from test_socket_ssl + Memcheck:Cond + fun:memcpy + fun:ssl3_read_bytes +} + +{ + from test_socket_ssl + Memcheck:Cond + fun:SHA1_Update +} + +{ + from test_socket_ssl + Memcheck:Value4 + fun:SHA1_Update +} +# +# DATADOG SUPPRESSIONS +# +{ + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyTypes_Init + fun:pycore_init_types + fun:pyinit_config +} + + + +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:_PyDict_NewPresized + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:_PyEval_EvalCodeWithName +} + + +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyCode_NewWithPosOnlyArgs + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:read_object + fun:marshal_loads_impl + fun:marshal_loads +} + +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:clone_combined_dict + fun:PyDict_Copy + fun:type_new + fun:type_call + fun:_PyObject_MakeTpCall + fun:_PyObject_Vectorcall + fun:_PyObject_FastCall + fun:_PyObject_CallFunctionVa + fun:PyObject_CallFunction +} + + +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object +} + +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicode_FromUCS1 + fun:PyUnicode_FromKindAndData + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyMem_Malloc + fun:PyCode_NewWithPosOnlyArgs + fun:makecode + fun:assemble + fun:compiler_function + fun:compiler_visit_stmt + fun:compiler_function + fun:compiler_visit_stmt + fun:compiler_body + fun:compiler_mod +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyMem_Malloc + fun:PyCode_NewWithPosOnlyArgs + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:read_object + fun:marshal_loads_impl + fun:marshal_loads +} +{ + + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyFloat_FromDouble + fun:fill_time + fun:_pystat_fromstructstat + fun:posix_do_stat + fun:os_stat_impl + fun:os_stat + fun:cfunction_vectorcall_FASTCALL_KEYWORDS + fun:_PyObject_Vectorcall + fun:call_function +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyLong_New + fun:PyLong_FromLong + fun:r_object + fun:r_object + fun:r_object + fun:PyMarshal_ReadObjectFromString + fun:get_frozen_object + fun:_imp_get_frozen_object_impl + fun:_imp_get_frozen_object +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyLong_New + fun:long_bitwise + fun:long_and + fun:binary_op1 + fun:binary_op + fun:PyNumber_And + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:function_code_fastcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyLong_New + fun:PyLong_FromLong + fun:setint + fun:PyInit_mmap + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl + fun:_imp_create_dynamic + fun:cfunction_vectorcall_FASTCALL + fun:PyVectorcall_Call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyLong_New + fun:PyLong_FromLong + fun:parsenumber_raw + fun:parsenumber + fun:ast_for_atom + fun:ast_for_atom_expr + fun:ast_for_power + fun:ast_for_expr + fun:ast_for_expr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyLong_New + fun:PyLong_FromLong + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:_PyUnicode_AsASCIIString + fun:PyUnicode_AsASCIIString + fun:cast_to_1D + fun:memory_cast + fun:method_vectorcall_VARARGS_KEYWORDS + fun:_PyObject_Vectorcall + fun:call_function +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:parsestr + fun:parsestrplus + fun:ast_for_atom + fun:ast_for_atom_expr + fun:ast_for_power + fun:ast_for_expr + fun:ast_for_ifexpr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:parsestr + fun:parsestrplus + fun:ast_for_atom + fun:ast_for_atom_expr + fun:ast_for_power + fun:ast_for_expr + fun:ast_for_expr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:parsestr + fun:parsestrplus + fun:ast_for_atom + fun:ast_for_atom_expr + fun:ast_for_power + fun:ast_for_expr + fun:ast_for_testlist +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:parsestr + fun:parsestrplus + fun:ast_for_atom + fun:ast_for_atom_expr + fun:ast_for_power + fun:ast_for_expr + fun:ast_for_arguments +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyBytes_Resize + fun:assemble + fun:compiler_function + fun:compiler_visit_stmt + fun:compiler_if + fun:compiler_visit_stmt + fun:compiler_function + fun:compiler_visit_stmt + fun:compiler_body +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyLong_New + fun:long_lshift1 + fun:long_lshift + fun:binary_op1 + fun:binary_op + fun:PyNumber_Lshift + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:_PyEval_EvalCodeWithName +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyBytes_Resize + fun:assemble + fun:compiler_function + fun:compiler_visit_stmt + fun:compiler_function + fun:compiler_visit_stmt + fun:compiler_function + fun:compiler_visit_stmt + fun:compiler_body +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyBytes_Resize + fun:assemble + fun:compiler_comprehension + fun:compiler_listcomp + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_call_helper + fun:compiler_call + fun:compiler_visit_expr1 +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyType_Ready + fun:_PyTypes_Init + fun:pycore_init_types + fun:pyinit_config + fun:pyinit_core +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyMem_Malloc + fun:new_dict_with_shared_keys + fun:_PyObjectDict_SetItem + fun:_PyObject_GenericSetAttrWithDict + fun:PyObject_GenericSetAttr + fun:wrap_setattr + fun:wrapperdescr_raw_call + fun:wrapperdescr_call + fun:_PyObject_MakeTpCall + fun:_PyObject_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyBytes_Resize + fun:assemble + fun:compiler_comprehension + fun:compiler_dictcomp + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_visit_stmt + fun:compiler_try_except + fun:compiler_try +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyTuple_FromArray + fun:PyList_AsTuple + fun:PySequence_Tuple + fun:mro_invoke + fun:mro_internal +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyUnicode_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyUnicode_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyUnicode_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyStructSequence_InitType2 + fun:_PyLong_Init + fun:pycore_init_types +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyExc_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyStructSequence_InitType2 + fun:_PyFloat_Init + fun:pycore_init_types +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyHamt_Init + fun:_PyContext_Init + fun:pycore_init_types +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyHamt_Init + fun:_PyContext_Init + fun:pycore_init_types +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyHamt_Init + fun:_PyContext_Init + fun:pycore_init_types +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyHamt_Init + fun:_PyContext_Init + fun:pycore_init_types +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyHamt_Init + fun:_PyContext_Init + fun:pycore_init_types +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyHamt_Init + fun:_PyContext_Init + fun:pycore_init_types +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyHamt_Init + fun:_PyContext_Init + fun:pycore_init_types +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyContext_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyContext_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyContext_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyContext_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyStructSequence_InitType2 + fun:_PyErr_Init + fun:pycore_init_types +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyModuleDef_Init + fun:_PyModule_CreateInitialized + fun:_PySys_Create +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyObject_GenericSetAttrWithDict + fun:PyObject_GenericSetAttr + fun:PyObject_SetAttr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyStructSequence_InitType2 + fun:_PySys_InitCore + fun:_PySys_Create +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyStructSequence_InitType2 + fun:_PySys_InitCore + fun:_PySys_Create +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyStructSequence_InitType2 + fun:_PySys_InitCore + fun:_PySys_Create +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyStructSequence_InitType2 + fun:PyThread_GetInfo + fun:_PySys_InitCore +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyStructSequence_InitType2 + fun:_PySys_InitCore + fun:_PySys_Create +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyBuiltin_Init + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyBuiltin_Init + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:_PyBuiltin_Init + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__thread + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__thread + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__thread + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__thread + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyStructSequence_InitType2 + fun:PyInit__thread + fun:_imp_create_builtin +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__io + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__io + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__io + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__io + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__io + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__io + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__io + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__io + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__io + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__io + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__io + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__io + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__io + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__io + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyStructSequence_NewType + fun:PyInit_posix + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyStructSequence_NewType + fun:PyInit_posix + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyStructSequence_NewType + fun:PyInit_posix + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyStructSequence_NewType + fun:PyInit_posix + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyStructSequence_NewType + fun:PyInit_posix + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit_posix + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit_posix + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyStructSequence_InitType2 + fun:PyInit_time + fun:_imp_create_builtin +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__abc + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__operator + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__operator + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__operator + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__sre + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__sre + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__sre + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__struct + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__struct + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyErr_NewException + fun:PyInit__struct + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__pickle + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__datetime + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__datetime + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__datetime + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__datetime + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__datetime + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__datetime + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:PyVectorcall_Call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__json + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__json + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__decimal + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__decimal + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__decimal + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__decimal + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit_zlib + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit_zlib + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyErr_NewException + fun:PyInit_zlib + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__bz2 + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__bz2 + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyErr_NewException + fun:PyErr_NewExceptionWithDoc + fun:PyInit__lzma + fun:_PyImport_LoadDynamicModuleWithSpec +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__lzma + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__lzma + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyStructSequence_InitType2 + fun:PyInit_grp + fun:_PyImport_LoadDynamicModuleWithSpec +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__sha512 + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__sha512 + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyErr_NewException + fun:PyInit__socket + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyErr_NewException + fun:PyInit__socket + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyErr_NewException + fun:PyInit__socket + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:add_ast_fields + fun:init_types + fun:PyAST_Check +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl + fun:builtin_compile +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl + fun:builtin_compile +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl + fun:builtin_compile +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl + fun:builtin_compile +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl + fun:builtin_compile +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl + fun:builtin_compile +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl + fun:builtin_compile +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl + fun:builtin_compile +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl + fun:builtin_compile +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl + fun:builtin_compile +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl + fun:builtin_compile +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl + fun:builtin_compile +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl + fun:builtin_compile +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl + fun:builtin_compile +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl + fun:builtin_compile +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl + fun:builtin_compile +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__lsprof + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyStructSequence_InitType2 + fun:PyInit__lsprof + fun:_PyImport_LoadDynamicModuleWithSpec +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyStructSequence_InitType2 + fun:PyInit__lsprof + fun:_PyImport_LoadDynamicModuleWithSpec +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit_mmap + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:PyInit__multiprocessing + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:dict_keys_inorder + fun:makecode + fun:assemble + fun:compiler_comprehension + fun:compiler_genexp +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:dict_keys_inorder + fun:makecode + fun:assemble + fun:compiler_function + fun:compiler_visit_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:compiler_call_helper + fun:compiler_call + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_visit_stmt_expr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:compiler_from_import + fun:compiler_visit_stmt + fun:compiler_function + fun:compiler_visit_stmt + fun:compiler_body +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:compiler_from_import + fun:compiler_visit_stmt + fun:compiler_if + fun:compiler_visit_stmt + fun:compiler_function +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:dict_keys_inorder + fun:makecode + fun:assemble + fun:compiler_comprehension + fun:compiler_dictcomp +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyFloat_FromDouble + fun:fill_time + fun:_pystat_fromstructstat + fun:posix_do_stat + fun:os_stat_impl + fun:os_stat + fun:cfunction_vectorcall_FASTCALL_KEYWORDS + fun:_PyObject_Vectorcall + fun:call_function +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyFloat_FromDouble + fun:r_object + fun:r_object + fun:r_object + fun:read_object + fun:marshal_loads_impl + fun:marshal_loads + fun:cfunction_vectorcall_O + fun:_PyObject_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:get_latin1_char + fun:_PyUnicode_FromUCS1 + fun:PyUnicode_FromKindAndData + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:get_latin1_char + fun:_PyUnicode_FromUCS1 + fun:PyUnicode_FromKindAndData + fun:r_object + fun:r_object + fun:r_object + fun:PyMarshal_ReadObjectFromString + fun:get_frozen_object +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:get_latin1_char + fun:_PyUnicode_FromUCS1 + fun:PyUnicode_FromKindAndData + fun:r_object + fun:r_object + fun:r_object + fun:read_object + fun:marshal_loads_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltin_Init + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltin_Init + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_ZN8pybind116detail9enum_base5valueEPKcNS_6objectES3_.constprop.0 + fun:_ZN8pybind115enum_I11SecureMarksE5valueEPKcS1_S4_.constprop.0 + fun:_ZL23pybind11_init__taintingRN8pybind117module_E + fun:PyInit__tainting +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_ZN8pybind116detail9enum_base5valueEPKcNS_6objectES3_.constprop.0 + fun:_ZN8pybind115enum_I17SecureMarksFilterE5valueEPKcS1_S4_.constprop.0 + fun:_ZL23pybind11_init__taintingRN8pybind117module_E + fun:PyInit__tainting +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyUnicode_InternFromString + fun:descr_new + fun:PyDescr_NewMember + fun:add_members +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PySys_InitCore + fun:_PySys_Create + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltin_Init + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltin_Init + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltin_Init + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltin_Init + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PySys_InitMain + fun:pyinit_main + fun:Py_InitializeFromConfig +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicode_FromUCS1 + fun:PyUnicode_FromKindAndData + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:read_object +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltin_Init + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltin_Init + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltin_Init + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:_PyUnicode_FromId + fun:_PyDict_SetItemId + fun:_PySys_SetObjectId + fun:init_sys_streams + fun:pyinit_main +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:_PyUnicode_FromId + fun:_PyDict_SetItemId + fun:_PySys_SetPreliminaryStderr + fun:_PySys_Create + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:PyModule_AddObject + fun:PyInit__thread + fun:_imp_create_builtin +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PySys_InitMain + fun:pyinit_main + fun:Py_InitializeFromConfig +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:_PyUnicode_FromId + fun:_PyDict_SetItemId + fun:_PySys_SetObjectId + fun:init_sys_streams + fun:pyinit_main +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:PyList_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyUnicode_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyUnicode_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyUnicode_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyHamt_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyHamt_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyHamt_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyHamt_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyHamt_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyHamt_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyHamt_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyContext_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyContext_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyContext_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyContext_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:PyModuleDef_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyObject_GenericSetAttrWithDict +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PySys_Create + fun:pyinit_config + fun:pyinit_core +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyBuiltin_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyBuiltin_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyBuiltin_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltin_Init + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:PyInit__thread +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:PyInit__thread +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:PyInit__io +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:PyInit__io +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:PyModule_AddObject + fun:PyModule_AddIntConstant + fun:init_timezone +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyType_GenericAlloc + fun:PyType_GenericNew + fun:type_call + fun:_PyObject_MakeTpCall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:_PyEval_EvalCodeWithName +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:parser_init + fun:_PyArg_UnpackKeywords + fun:_io_open + fun:cfunction_vectorcall_FASTCALL_KEYWORDS +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:PyInit__operator +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:PyInit__collections +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:PyInit__collections +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:PyInit__sre +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:PyInit__struct +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:PyInit__pickle +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:PyInit__pickle +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:PyInit__pickle +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:PyInit__pickle +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:PyInit__datetime +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:PyInit__datetime +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:_PyCode_ConstantKey + fun:merge_const_tuple + fun:makecode + fun:assemble +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:PyInit_zlib +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:PyInit__sha512 +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyTuple_FromArray + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:method_vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_ZN8pybind116detail9enum_base5valueEPKcNS_6objectES3_.constprop.0 + fun:_ZN8pybind115enum_I13ParameterTypeE5valueEPKcS1_S4_.constprop.0 + fun:_ZL23pybind11_init__taintingRN8pybind117module_E + fun:PyInit__tainting + fun:_PyImport_LoadDynamicModuleWithSpec +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_ZN8pybind116detail9enum_base5valueEPKcNS_6objectES3_.constprop.0 + fun:_ZN8pybind115enum_I13ParameterTypeE5valueEPKcS1_S4_.constprop.0 + fun:_ZL23pybind11_init__taintingRN8pybind117module_E + fun:PyInit__tainting + fun:_PyImport_LoadDynamicModuleWithSpec +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_ZN8pybind116detail9enum_base5valueEPKcNS_6objectES3_.constprop.0 + fun:_ZN8pybind115enum_I13ParameterTypeE5valueEPKcS1_S4_.constprop.0 + fun:_ZL23pybind11_init__taintingRN8pybind117module_E + fun:PyInit__tainting + fun:_PyImport_LoadDynamicModuleWithSpec +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_ZN8pybind116detail9enum_base5valueEPKcNS_6objectES3_.constprop.0 + fun:_ZN8pybind115enum_I13ParameterTypeE5valueEPKcS1_S4_.constprop.0 + fun:_ZL23pybind11_init__taintingRN8pybind117module_E + fun:PyInit__tainting + fun:_PyImport_LoadDynamicModuleWithSpec +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_ZN8pybind116detail9enum_base5valueEPKcNS_6objectES3_.constprop.0 + fun:_ZN8pybind115enum_I13ParameterTypeE5valueEPKcS1_S4_.constprop.0 + fun:_ZL23pybind11_init__taintingRN8pybind117module_E + fun:PyInit__tainting + fun:_PyImport_LoadDynamicModuleWithSpec +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_ZN8pybind116detail9enum_base5valueEPKcNS_6objectES3_.constprop.0 + fun:_ZN8pybind115enum_I13ParameterTypeE5valueEPKcS1_S4_.constprop.0 + fun:_ZL23pybind11_init__taintingRN8pybind117module_E + fun:PyInit__tainting + fun:_PyImport_LoadDynamicModuleWithSpec +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_ZN8pybind116detail9enum_base5valueEPKcNS_6objectES3_.constprop.0 + fun:_ZN8pybind115enum_I13ParameterTypeE5valueEPKcS1_S4_.constprop.0 + fun:_ZL23pybind11_init__taintingRN8pybind117module_E + fun:PyInit__tainting + fun:_PyImport_LoadDynamicModuleWithSpec +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_ZN8pybind116detail9enum_base5valueEPKcNS_6objectES3_.constprop.0 + fun:_ZN8pybind115enum_I13ParameterTypeE5valueEPKcS1_S4_.constprop.0 + fun:_ZL23pybind11_init__taintingRN8pybind117module_E + fun:PyInit__tainting + fun:_PyImport_LoadDynamicModuleWithSpec +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_ZN8pybind116detail9enum_base5valueEPKcNS_6objectES3_.constprop.0 + fun:_ZN8pybind115enum_I11SecureMarksE5valueEPKcS1_S4_.constprop.0 + fun:_ZL23pybind11_init__taintingRN8pybind117module_E + fun:PyInit__tainting + fun:_PyImport_LoadDynamicModuleWithSpec +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_ZN8pybind116detail9enum_base5valueEPKcNS_6objectES3_.constprop.0 + fun:_ZN8pybind115enum_I11SecureMarksE5valueEPKcS1_S4_.constprop.0 + fun:_ZL23pybind11_init__taintingRN8pybind117module_E + fun:PyInit__tainting + fun:_PyImport_LoadDynamicModuleWithSpec +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_ZN8pybind116detail9enum_base5valueEPKcNS_6objectES3_.constprop.0 + fun:_ZN8pybind115enum_I11SecureMarksE5valueEPKcS1_S4_.constprop.0 + fun:_ZL23pybind11_init__taintingRN8pybind117module_E + fun:PyInit__tainting + fun:_PyImport_LoadDynamicModuleWithSpec +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_ZN8pybind116detail9enum_base5valueEPKcNS_6objectES3_.constprop.0 + fun:_ZN8pybind115enum_I17SecureMarksFilterE5valueEPKcS1_S4_.constprop.0 + fun:_ZL23pybind11_init__taintingRN8pybind117module_E + fun:PyInit__tainting + fun:_PyImport_LoadDynamicModuleWithSpec +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_ZN8pybind116detail9enum_base5valueEPKcNS_6objectES3_.constprop.0 + fun:_ZN8pybind115enum_I17SecureMarksFilterE5valueEPKcS1_S4_.constprop.0 + fun:_ZL23pybind11_init__taintingRN8pybind117module_E + fun:PyInit__tainting + fun:_PyImport_LoadDynamicModuleWithSpec +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_ZN8pybind116detail9enum_base5valueEPKcNS_6objectES3_.constprop.0 + fun:_ZN8pybind115enum_I17SecureMarksFilterE5valueEPKcS1_S4_.constprop.0 + fun:_ZL23pybind11_init__taintingRN8pybind117module_E + fun:PyInit__tainting + fun:_PyImport_LoadDynamicModuleWithSpec +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:dict_keys_inorder + fun:makecode + fun:assemble + fun:compiler_comprehension + fun:compiler_genexp +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:compiler_call_helper + fun:compiler_call + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_call_helper +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyCode_ConstantKey + fun:merge_const_tuple + fun:makecode + fun:assemble + fun:compiler_lambda +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PySys_InitCore + fun:_PySys_Create + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PySys_InitCore + fun:_PySys_Create + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltin_Init + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltin_Init + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:PyInit__thread + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:PyModule_AddObject + fun:PyModule_AddIntConstant + fun:init_timezone +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:PyModule_AddObject + fun:PyModule_AddIntConstant + fun:init_timezone +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:create_stdio + fun:init_sys_streams + fun:pyinit_main + fun:Py_InitializeFromConfig +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PySys_InitCore + fun:_PySys_Create + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PySys_InitCore + fun:_PySys_Create + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PySys_InitCore + fun:_PySys_Create + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltin_Init + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltin_Init + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:PySys_SetObject + fun:_PyImportHooks_Init + fun:pycore_init_import_warnings +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PySys_InitMain + fun:pyinit_main + fun:Py_InitializeFromConfig +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:PySys_SetObject + fun:init_sys_streams + fun:pyinit_main +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PySys_SetPreliminaryStderr + fun:_PySys_Create + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PySys_InitCore + fun:_PySys_Create + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PySys_InitCore + fun:_PySys_Create + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PySys_InitCore + fun:_PySys_Create + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PySys_InitCore + fun:_PySys_Create + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltin_Init + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:PySys_SetObject + fun:_PyImportHooks_Init + fun:pycore_init_import_warnings +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PySys_InitMain + fun:pyinit_main + fun:Py_InitializeFromConfig +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:PySys_SetObject + fun:init_sys_streams + fun:pyinit_main +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PySys_InitCore + fun:_PySys_Create + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PySys_InitCore + fun:_PySys_Create + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltin_Init + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PySys_InitMain + fun:pyinit_main + fun:Py_InitializeFromConfig +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PySys_InitMain + fun:pyinit_main + fun:Py_InitializeFromConfig +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PySys_InitMain + fun:pyinit_main + fun:Py_InitializeFromConfig +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:PyModule_AddObject + fun:PyInit_time + fun:_imp_create_builtin +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PySys_InitCore + fun:_PySys_Create + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltin_Init + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyType_Ready + fun:PyInit__ctypes + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PySys_InitCore + fun:_PySys_Create + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PySys_InitCore + fun:_PySys_Create + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltin_Init + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PySys_InitMain + fun:pyinit_main + fun:Py_InitializeFromConfig +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:PyModule_AddObject + fun:PyModule_AddIntConstant + fun:PyInit_time +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:PyModule_AddObject + fun:PyModule_AddIntConstant + fun:PyInit_time +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_DecodeUTF8 + fun:new_identifier + fun:alias_for_import_name + fun:ast_for_import_stmt + fun:ast_for_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:PyCode_Optimize + fun:makecode + fun:assemble + fun:compiler_comprehension + fun:compiler_dictcomp + fun:compiler_visit_expr1 + fun:compiler_visit_expr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:PyUnicode_InternInPlace + fun:PyUnicode_InternFromString + fun:init_slotdefs + fun:add_operators +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyTypes_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:PyType_Ready + fun:PyModuleDef_Init + fun:_PyModule_CreateInitialized + fun:_PySys_Create +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:PyModule_NewObject + fun:PyModule_New + fun:_PyModule_CreateInitialized + fun:_PySys_Create +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PySys_InitCore + fun:_PySys_Create + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:PyModule_NewObject + fun:PyModule_New + fun:_PyModule_CreateInitialized + fun:_PyBuiltin_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:_Warnings_InitState + fun:_PyWarnings_Init + fun:pycore_init_import_warnings + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:type_prepare + fun:cfunction_vectorcall_FASTCALL_KEYWORDS + fun:_PyObject_FastCallDict + fun:builtin___build_class__ +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:PyType_Ready + fun:PyInit__thread + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:PyModule_AddObject + fun:PyInit__thread + fun:_imp_create_builtin +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:do_mkdict + fun:do_mkvalue + fun:do_mkstack + fun:va_build_stack +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:PyInit__io +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:PyInit__io +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:PyType_Ready + fun:PyInit__io + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:PyInit_posix + fun:_imp_create_builtin + fun:cfunction_vectorcall_O + fun:PyVectorcall_Call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:PyModule_AddObject + fun:PyInit_posix + fun:_imp_create_builtin +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:PyModule_AddObject + fun:PyModule_AddIntConstant + fun:PyInit_time +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyTuple_FromArray + fun:tupleslice + fun:PyTuple_GetSlice + fun:tp_new_wrapper + fun:cfunction_call_varargs +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:compiler_enter_scope + fun:compiler_mod + fun:PyAST_CompileObject + fun:run_mod +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:ste_new + fun:symtable_enter_block + fun:PySymtable_BuildObject + fun:PyAST_CompileObject +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyTuple_FromArray + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:method_vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyCode_ConstantKey + fun:merge_const_tuple + fun:makecode + fun:assemble + fun:compiler_mod +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_DecodeUTF8 + fun:new_identifier + fun:ast_for_call + fun:ast_for_trailer + fun:ast_for_atom_expr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:PyFrame_FastToLocalsWithError + fun:PyEval_GetLocals + fun:builtin_locals_impl + fun:builtin_locals +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:PyType_Ready + fun:PyInit__pickle + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:new_dict_with_shared_keys + fun:_PyObjectDict_SetItem + fun:_PyObject_GenericSetAttrWithDict + fun:PyObject_GenericSetAttr + fun:wrap_setattr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:method_vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyStack_UnpackDict + fun:_PyObject_FastCallDict + fun:partial_fastcall + fun:partial_call + fun:_PyObject_MakeTpCall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyContextVar_New + fun:PyInit__decimal + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:dictbytype + fun:compiler_enter_scope + fun:compiler_mod + fun:PyAST_CompileObject +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:compiler_enter_scope + fun:compiler_mod + fun:PyAST_CompileObject + fun:Py_CompileStringObject +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:compiler_enter_scope + fun:compiler_mod + fun:PyAST_CompileObject + fun:Py_CompileStringObject +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:_PyDict_NewPresized + fun:_PyStack_AsDict + fun:_PyObject_MakeTpCall + fun:_PyObject_Vectorcall + fun:call_function +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:PyType_Ready + fun:PyInit__ctypes + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:PyType_Ready + fun:PyInit__ctypes + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:clone_combined_dict + fun:PyDict_Copy + fun:type_new + fun:PyCArrayType_new + fun:type_call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:new_dict_with_shared_keys + fun:PyObject_GenericGetDict + fun:getset_get + fun:_PyObject_GenericGetAttrWithDict + fun:PyObject_GenericGetAttr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyCode_ConstantKey + fun:merge_consts_recursive + fun:compiler_add_const + fun:compiler_addop_load_const + fun:compiler_subkwargs +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:compiler_call_helper + fun:compiler_call + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_return +} +{ + + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + obj:/home/alberto/projects/hdiv-python-agent/hdiv_agent/manualpackages/orjson/orjson.cpython-38-x86_64-linux-gnu.so + obj:/home/alberto/projects/hdiv-python-agent/hdiv_agent/manualpackages/orjson/orjson.cpython-38-x86_64-linux-gnu.so + obj:/home/alberto/projects/hdiv-python-agent/hdiv_agent/manualpackages/orjson/orjson.cpython-38-x86_64-linux-gnu.so + fun:PyInit_orjson +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PySys_InitCore + fun:_PySys_Create + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PySys_InitMain + fun:pyinit_main + fun:Py_InitializeFromConfig +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PySys_InitMain + fun:pyinit_main + fun:Py_InitializeFromConfig +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:PyModule_AddObject + fun:PyModule_AddIntConstant + fun:PyInit_time +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromStringAndSize + fun:do_mkvalue + fun:do_mkstack + fun:va_build_stack + fun:_Py_VaBuildStack +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:PyCode_Optimize + fun:makecode + fun:assemble + fun:compiler_function + fun:compiler_visit_stmt + fun:compiler_if + fun:compiler_visit_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PySys_InitCore + fun:_PySys_Create + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PySys_InitCore + fun:_PySys_Create + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyType_Ready + fun:PyInit__thread + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:PySys_SetObject + fun:_PyImportHooks_Init + fun:pycore_init_import_warnings +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:PyModule_AddObject + fun:PyModule_AddIntConstant + fun:PyInit_time +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_ZN8pybind116detail9enum_base5valueEPKcNS_6objectES3_.constprop.0 + fun:_ZN8pybind115enum_I17SecureMarksFilterE5valueEPKcS1_S4_.constprop.0 + fun:_ZL23pybind11_init__taintingRN8pybind117module_E + fun:PyInit__tainting +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_ZN8pybind116detail9enum_base5valueEPKcNS_6objectES3_.constprop.0 + fun:_ZN8pybind115enum_I17SecureMarksFilterE5valueEPKcS1_S4_.constprop.0 + fun:_ZL23pybind11_init__taintingRN8pybind117module_E + fun:PyInit__tainting +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PySys_InitCore + fun:_PySys_Create + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:PyInit__imp + fun:init_importlib + fun:pycore_init_import_warnings +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:PyInit__io +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:PyInit__io +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:PyInit__io +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:PyInit__io +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:PyInit__io +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:PyInit__io +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:PyInit__io +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:PyInit__io +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:PyModule_AddObject + fun:PyModule_AddIntConstant + fun:PyInit_time +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:add_attributes + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl + fun:builtin_compile +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl + fun:builtin_compile +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl + fun:builtin_compile +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:add_attributes + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl + fun:builtin_compile +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl + fun:builtin_compile +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:add_attributes + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl + fun:builtin_compile +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:add_attributes + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl + fun:builtin_compile +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:PyModule_AddObject + fun:PyModule_AddIntConstant + fun:PyInit_time +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:_PyBuiltins_AddExceptions + fun:pycore_init_builtins + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyBytes_Resize + fun:assemble + fun:compiler_comprehension + fun:compiler_genexp + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_call_helper + fun:compiler_call + fun:compiler_visit_expr1 +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyBytes_Resize + fun:assemble + fun:compiler_comprehension + fun:compiler_listcomp + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_visit_stmt + fun:compiler_function + fun:compiler_visit_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyStack_UnpackDict + fun:_PyObject_FastCallDict + fun:_PyObject_Call_Prepend + fun:slot_tp_new + fun:type_call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyMem_Malloc + fun:new_dict_with_shared_keys + fun:_PyObjectDict_SetItem + fun:_PyObject_GenericSetAttrWithDict + fun:PyObject_GenericSetAttr + fun:wrap_setattr + fun:wrapperdescr_raw_call + fun:wrapper_call + fun:_PyObject_MakeTpCall + fun:_PyObject_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:contextvar_new + fun:PyContextVar_New + fun:PyInit__decimal + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl + fun:_imp_create_dynamic +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:type_mro_impl + fun:type_mro + fun:cfunction_vectorcall_NOARGS + fun:_PyObject_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl + fun:builtin_compile +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl + fun:builtin_compile +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyLong_New + fun:PyLong_FromLong + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:read_object + fun:marshal_loads_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:resize_compact + fun:unicode_resize + fun:PyUnicode_Append + fun:compiler_set_qualname + fun:compiler_enter_scope + fun:compiler_comprehension + fun:compiler_dictcomp + fun:compiler_visit_expr1 + fun:compiler_visit_expr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:_PyExc_Init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl + fun:builtin_compile +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:find_name_in_mro + fun:_PyType_Lookup + fun:_PyType_LookupId +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:_PyEval_EvalCodeWithName + fun:PyEval_EvalCodeEx + fun:builtin___build_class__ +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:_PyErr_SetKeyError + fun:dict_subscript + fun:PyObject_GetItem + fun:_PyEval_EvalFrameDefault +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:type_new + fun:tp_new_wrapper + fun:cfunction_call_varargs + fun:PyCFunction_Call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:map_new + fun:type_call + fun:_PyObject_MakeTpCall + fun:_PyObject_Vectorcall + fun:call_function +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyCode_ConstantKey + fun:merge_consts_recursive + fun:compiler_add_const + fun:compiler_addop_load_const + fun:compiler_from_import +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:dict_keys_inorder + fun:makecode + fun:assemble + fun:compiler_comprehension + fun:compiler_listcomp +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:get_latin1_char + fun:_PyUnicode_FromUCS1 + fun:PyUnicode_FromKindAndData + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:read_object +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyLong_New + fun:r_PyLong + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:parsestr + fun:parsestrplus + fun:ast_for_atom + fun:ast_for_atom_expr + fun:ast_for_power + fun:ast_for_expr + fun:ast_for_dictelement +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:PyMarshal_ReadObjectFromString +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:function_code_fastcall + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:unicode_repeat + fun:sequence_repeat + fun:PyNumber_Multiply + fun:safe_multiply + fun:safe_multiply + fun:fold_binop + fun:astfold_expr + fun:astfold_expr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:PyImport_Cleanup + fun:Py_FinalizeEx + fun:Py_RunMain +} +{ + + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:PyInit_orjson + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl + fun:_imp_create_dynamic + fun:cfunction_vectorcall_FASTCALL + fun:PyVectorcall_Call + fun:PyCFunction_Call + fun:do_call_core + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:_PyEval_EvalCodeWithName +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicode_FromUCS1 + fun:PyUnicode_FromKindAndData + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:PyMarshal_ReadObjectFromString +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicode_FromUCS1 + fun:PyUnicode_FromKindAndData + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:PyMarshal_ReadObjectFromString +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:find_name_in_mro +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:long_divmod + fun:binary_op1 + fun:binary_op + fun:PyNumber_Divmod + fun:builtin_divmod_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:compiler_call_helper + fun:compiler_call + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:maybe_optimize_method_call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_DecodeUTF8 + fun:new_identifier + fun:ast_for_trailer + fun:ast_for_atom_expr + fun:ast_for_power +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:PyCode_Optimize + fun:makecode + fun:assemble + fun:compiler_comprehension + fun:compiler_genexp + fun:compiler_visit_expr1 + fun:compiler_visit_expr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyCode_ConstantKey + fun:merge_const_tuple + fun:makecode + fun:assemble + fun:compiler_comprehension +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:strdup + fun:_Z18copy_string_new_idRKN8pybind115bytesE + fun:_ZZN8pybind1112cpp_function10initializeIRPFNS_5bytesERKS2_ES2_JS4_EJNS_4nameENS_5scopeENS_7siblingENS_3argENS_19return_value_policyEEEEvOT_PFT0_DpT1_EDpRKT2_ENUlRNS_6detail13function_callEE1_4_FUNESQ_ + fun:_ZN8pybind1112cpp_function10dispatcherEP7_objectS2_S2_ + fun:cfunction_call_varargs + fun:PyCFunction_Call + fun:_PyObject_MakeTpCall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:type_prepare + fun:cfunction_vectorcall_FASTCALL_KEYWORDS + fun:_PyObject_FastCallDict + fun:builtin___build_class__ +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:dictbytype + fun:compiler_enter_scope + fun:compiler_mod + fun:PyAST_CompileObject +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:_PyDict_NewPresized + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:function_code_fastcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:method_vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:read_object + fun:marshal_loads_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:compiler_call_helper + fun:compiler_call + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_visit_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyCode_ConstantKey + fun:merge_const_tuple + fun:makecode + fun:assemble + fun:compiler_comprehension +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyFloat_FromDouble + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:read_object +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicode_FromUCS1 + fun:PyUnicode_FromKindAndData + fun:r_object + fun:r_object + fun:r_object + fun:read_object + fun:marshal_loads_impl + fun:marshal_loads +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyBytes_Resize + fun:assemble + fun:compiler_comprehension + fun:compiler_listcomp + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:maybe_optimize_method_call + fun:compiler_call + fun:compiler_visit_expr1 +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:fold_tuple_on_constants + fun:PyCode_Optimize + fun:makecode + fun:assemble + fun:compiler_class +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:sys_getdefaultencoding_impl + fun:sys_getdefaultencoding + fun:cfunction_vectorcall_NOARGS + fun:_PyObject_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:resize_compact + fun:unicode_resize + fun:PyUnicode_Append + fun:compiler_set_qualname + fun:compiler_enter_scope + fun:compiler_comprehension + fun:compiler_genexp + fun:compiler_visit_expr1 + fun:compiler_visit_expr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:add_operators + fun:PyType_Ready + fun:PyStructSequence_InitType2 + fun:_PyErr_Init + fun:pycore_init_types + fun:pyinit_config +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:clone_combined_dict + fun:PyDict_Copy + fun:set_names + fun:type_new + fun:type_call + fun:_PyObject_MakeTpCall + fun:_PyObject_Vectorcall + fun:_PyObject_FastCall + fun:_PyObject_CallFunctionVa +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:_PyDict_SetItemId + fun:PyType_Ready + fun:PyInit__io + fun:_imp_create_builtin + fun:cfunction_vectorcall_O + fun:PyVectorcall_Call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:_PyStack_AsDict + fun:builtin___build_class__ + fun:cfunction_vectorcall_FASTCALL_KEYWORDS + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:_PyObjectDict_SetItem + fun:_PyObject_GenericSetAttrWithDict + fun:PyObject_GenericSetAttr + fun:PyObject_SetAttr + fun:_PyObject_SetAttrId + fun:create_stdio +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:do_mkdict + fun:do_mkvalue + fun:va_build_value + fun:Py_BuildValue + fun:PyImport_Import + fun:PyImport_ImportModule +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:analyze_name + fun:analyze_block + fun:symtable_analyze + fun:PySymtable_BuildObject + fun:PyAST_CompileObject + fun:run_mod +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:compiler_add_o + fun:compiler_add_const + fun:compiler_addop_load_const + fun:compiler_import + fun:compiler_visit_stmt + fun:compiler_body +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:symtable_add_def_helper + fun:symtable_add_def + fun:symtable_visit_stmt + fun:PySymtable_BuildObject + fun:PyAST_CompileObject + fun:run_mod +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:analyze_name + fun:analyze_block + fun:analyze_child_block + fun:analyze_block + fun:symtable_analyze + fun:PySymtable_BuildObject +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:compiler_add_o + fun:compiler_nameop + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_call + fun:compiler_visit_expr1 +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetDefault + fun:merge_const_tuple + fun:makecode + fun:assemble + fun:compiler_function + fun:compiler_visit_stmt + fun:compiler_body +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:clone_combined_dict + fun:PyDict_Copy + fun:partial_call + fun:_PyObject_MakeTpCall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:_PyEval_EvalCodeWithName +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:dict_ass_sub + fun:PyObject_SetItem + fun:map_to_dict + fun:PyFrame_FastToLocalsWithError + fun:PyEval_GetLocals + fun:builtin_exec_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:clone_combined_dict + fun:PyDict_Copy + fun:set_names + fun:type_new + fun:tp_new_wrapper + fun:cfunction_call_varargs + fun:PyCFunction_Call + fun:_PyObject_MakeTpCall + fun:_PyObject_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:dict_ass_sub + fun:PyObject_SetItem + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:dict_ass_sub + fun:PyObject_SetItem + fun:map_to_dict + fun:PyFrame_FastToLocalsWithError + fun:PyEval_GetLocals + fun:builtin_locals_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:clone_combined_dict + fun:PyDict_Copy + fun:type_new + fun:tp_new_wrapper + fun:cfunction_call_varargs + fun:PyCFunction_Call + fun:_PyObject_MakeTpCall + fun:_PyObject_Vectorcall + fun:call_function +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetDefault + fun:merge_const_tuple + fun:makecode + fun:assemble + fun:compiler_mod + fun:PyAST_CompileObject + fun:Py_CompileStringObject +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:add_operators + fun:PyType_Ready + fun:PyInit__ctypes + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl + fun:_imp_create_dynamic +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:symtable_add_def_helper + fun:symtable_add_def + fun:symtable_visit_stmt + fun:symtable_visit_stmt + fun:PySymtable_BuildObject + fun:PyAST_CompileObject +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:compiler_add_o + fun:compiler_nameop + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:maybe_optimize_method_call + fun:compiler_call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:symtable_add_def_helper + fun:symtable_add_def + fun:symtable_implicit_arg + fun:symtable_handle_comprehension + fun:symtable_visit_genexp + fun:symtable_visit_expr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:read_object +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyTuple_FromArray + fun:PyList_AsTuple + fun:makecode + fun:assemble + fun:compiler_comprehension +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:_PyCode_ConstantKey + fun:merge_consts_recursive + fun:compiler_add_const + fun:compiler_addop_load_const +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicode_FromASCII + fun:asciilib_rpartition + fun:PyUnicode_RPartition + fun:unicode_rpartition + fun:method_vectorcall_O + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyCode_NewWithPosOnlyArgs + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:PyMarshal_ReadObjectFromString +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyCode_NewWithPosOnlyArgs + fun:makecode + fun:assemble + fun:compiler_function + fun:compiler_visit_stmt + fun:compiler_function + fun:compiler_visit_stmt + fun:compiler_function + fun:compiler_visit_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyCode_NewWithPosOnlyArgs + fun:makecode + fun:assemble + fun:compiler_comprehension + fun:compiler_listcomp + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_call_helper + fun:compiler_call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyCode_NewWithPosOnlyArgs + fun:makecode + fun:assemble + fun:compiler_comprehension + fun:compiler_dictcomp + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_visit_stmt + fun:compiler_try_except +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyCode_NewWithPosOnlyArgs + fun:makecode + fun:assemble + fun:compiler_function + fun:compiler_visit_stmt + fun:compiler_if + fun:compiler_visit_stmt + fun:compiler_function + fun:compiler_visit_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:type_new +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:_PyEval_EvalCodeWithName + fun:PyEval_EvalCodeEx + fun:PyEval_EvalCode +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:_PyBytesWriter_Finish + fun:_PyBytes_DecodeEscape + fun:decode_bytes_with_escapes + fun:parsestr + fun:parsestrplus + fun:ast_for_atom + fun:ast_for_atom_expr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:r_object + fun:r_object + fun:r_object + fun:read_object + fun:marshal_loads_impl + fun:marshal_loads + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyTuple_FromArray + fun:PyList_AsTuple + fun:type_new + fun:tp_new_wrapper + fun:cfunction_call_varargs +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_DecodeUTF8 + fun:new_identifier + fun:alias_for_import_name + fun:ast_for_import_stmt + fun:ast_for_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyBytes_Resize + fun:assemble + fun:compiler_function + fun:compiler_visit_stmt + fun:compiler_function + fun:compiler_visit_stmt + fun:compiler_body + fun:compiler_mod + fun:PyAST_CompileObject +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_DecodeUTF8 + fun:new_identifier + fun:ast_for_funcdef_impl + fun:ast_for_funcdef + fun:ast_for_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:compiler_enter_scope + fun:compiler_mod + fun:PyAST_CompileObject + fun:run_mod +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PySequence_Tuple + fun:tuple_new_impl + fun:tuple_new + fun:type_call + fun:_PyObject_MakeTpCall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:compiler_init + fun:PyAST_CompileObject + fun:run_mod + fun:PyRun_StringFlags +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:list2dict + fun:compiler_enter_scope + fun:compiler_function + fun:compiler_visit_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:dictbytype + fun:compiler_enter_scope + fun:compiler_function + fun:compiler_visit_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:dictbytype + fun:compiler_enter_scope + fun:compiler_function + fun:compiler_visit_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:compiler_enter_scope + fun:compiler_function + fun:compiler_visit_stmt + fun:compiler_body +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyTuple_FromArray + fun:_PyObject_MakeTpCall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyCode_ConstantKey + fun:merge_const_tuple + fun:makecode + fun:assemble + fun:compiler_mod +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:dict_keys_inorder + fun:makecode + fun:assemble + fun:compiler_mod + fun:PyAST_CompileObject +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromStringAndSize + fun:PyErr_NewException + fun:PyInit__decimal + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:resize_compact + fun:_PyUnicodeWriter_Finish + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_DecodeUTF8 + fun:r_object + fun:r_object + fun:r_object + fun:r_object +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyCode_ConstantKey + fun:merge_const_tuple + fun:makecode + fun:assemble + fun:compiler_function +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:dict_keys_inorder + fun:makecode + fun:assemble + fun:compiler_comprehension + fun:compiler_listcomp +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:add_attributes + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:add_attributes + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:add_attributes + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:add_attributes + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:dict_keys_inorder + fun:makecode + fun:assemble + fun:compiler_comprehension + fun:compiler_listcomp +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:asciilib_rpartition + fun:PyUnicode_RPartition + fun:unicode_rpartition + fun:method_vectorcall_O + fun:_PyObject_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:analyze_block + fun:symtable_analyze + fun:PySymtable_BuildObject + fun:PyAST_CompileObject +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:PyObject_GenericGetDict + fun:getset_get + fun:_PyObject_GenericGetAttrWithDict + fun:_PyObject_LookupAttr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:func_get_annotations + fun:getset_get + fun:_PyObject_GenericGetAttrWithDict + fun:PyObject_GenericGetAttr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:type_getattro +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:_PyCode_ConstantKey + fun:merge_consts_recursive + fun:compiler_add_const + fun:compiler_addop_load_const +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyMem_Malloc + fun:new_dict_with_shared_keys + fun:_PyObjectDict_SetItem + fun:_PyObject_GenericSetAttrWithDict + fun:PyObject_GenericSetAttr + fun:PyObject_SetAttr + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:_PyErr_SetKeyError + fun:_PyDict_DelItem_KnownHash + fun:PyDict_DelItem + fun:dict_ass_sub +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyTuple_FromArray + fun:_PyObject_MakeTpCall + fun:_PyObject_Vectorcall + fun:_PyObject_FastCall + fun:map_next +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:dictresize + fun:insertion_resize + fun:insertdict + fun:PyDict_SetItem + fun:dict_ass_sub + fun:PyObject_SetItem + fun:_ZN8pybind116detail9enum_base5valueEPKcNS_6objectES3_.constprop.0 + fun:_ZN8pybind115enum_I13ParameterTypeE5valueEPKcS1_S4_.constprop.0 +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyCode_ConstantKey + fun:merge_const_tuple + fun:makecode + fun:assemble + fun:compiler_function +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyTuple_FromArray + fun:method_vectorcall_VARARGS_KEYWORDS + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyTuple_FromArray + fun:_PyObject_MakeTpCall + fun:_PyObject_Vectorcall + fun:_PyObject_FastCall + fun:object_vacall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyTuple_FromArray + fun:PyList_AsTuple + fun:PySequence_Tuple + fun:type_new + fun:type_call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:_PyDict_SetItemId + fun:module_init_dict + fun:PyModule_NewObject + fun:PyModule_New + fun:_PyModule_CreateInitialized + fun:PyModule_Create2 +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:ste_new + fun:symtable_enter_block + fun:PySymtable_BuildObject + fun:PyAST_CompileObject + fun:run_mod + fun:PyRun_StringFlags +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:dict_new + fun:type_call + fun:_PyObject_MakeTpCall + fun:_PyObject_Vectorcall + fun:_PyObject_CallNoArg + fun:_PyDict_FromKeys + fun:dict_fromkeys_impl + fun:dict_fromkeys +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:_PyCode_ConstantKey + fun:_PyCode_ConstantKey + fun:merge_const_tuple + fun:makecode +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:resize_compact + fun:unicode_resize + fun:PyUnicode_Append + fun:PyUnicode_AppendAndDel + fun:FstringParser_ConcatAndDel + fun:parsestrplus + fun:ast_for_atom + fun:ast_for_atom_expr + fun:ast_for_power +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:resize_compact + fun:unicode_resize + fun:PyUnicode_Append + fun:compiler_set_qualname + fun:compiler_enter_scope + fun:compiler_function + fun:compiler_visit_stmt + fun:compiler_function + fun:compiler_visit_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyCode_NewWithPosOnlyArgs + fun:makecode + fun:assemble + fun:compiler_comprehension + fun:compiler_genexp + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_call_helper + fun:compiler_call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyCode_NewWithPosOnlyArgs + fun:makecode + fun:assemble + fun:compiler_comprehension + fun:compiler_listcomp + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_visit_stmt + fun:compiler_function +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyTuple_FromArray + fun:PyList_AsTuple + fun:type_new + fun:type_call + fun:_PyObject_MakeTpCall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyLong_New + fun:PyLong_FromLong + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:PyObject_GenericGetDict + fun:getset_get + fun:_PyObject_GenericGetAttrWithDict + fun:PyObject_GenericGetAttr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_FastCallDict + fun:_PyObject_Call_Prepend +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_FastCallDict + fun:_PyObject_Call_Prepend +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:PyType_GenericAlloc + fun:dict_new + fun:type_call + fun:_PyObject_MakeTpCall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:do_mktuple + fun:va_build_value + fun:Py_BuildValue + fun:ast_type_reduce + fun:cfunction_vectorcall_NOARGS +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:_PyCode_ConstantKey + fun:_PyCode_ConstantKey + fun:_PyCode_ConstantKey + fun:merge_const_tuple +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:make_type + fun:init_types + fun:PyAST_Check + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:function_code_fastcall + fun:_PyFunction_Vectorcall + fun:PyVectorcall_Call + fun:PyObject_Call + fun:do_call_core +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_sre_compile_impl + fun:_sre_compile + fun:cfunction_vectorcall_FASTCALL_KEYWORDS + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:method_vectorcall + fun:_PyObject_FastCallDict +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:resize_compact + fun:unicode_resize + fun:PyUnicode_Append + fun:compiler_set_qualname + fun:compiler_enter_scope + fun:compiler_comprehension + fun:compiler_listcomp + fun:compiler_visit_expr1 + fun:compiler_visit_expr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:PyEval_EvalCodeEx + fun:builtin___build_class__ + fun:cfunction_vectorcall_FASTCALL_KEYWORDS + fun:_PyObject_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:dict_keys_inorder + fun:makecode + fun:assemble + fun:compiler_class + fun:compiler_visit_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:type_getattro + fun:PyObject_GetAttr + fun:_PyEval_EvalFrameDefault +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicode_JoinArray + fun:PyUnicode_Join + fun:unicode_join + fun:method_vectorcall_O + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:PyModule_NewObject + fun:PyModule_New + fun:_PyModule_CreateInitialized + fun:PyModule_Create2 +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyObject_GC_Resize + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_FastCallDict + fun:_PyObject_Call_Prepend + fun:slot_tp_new + fun:type_call + fun:_PyObject_MakeTpCall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:method_vectorcall + fun:_PyObject_FastCallDict +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:function_code_fastcall + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:method_vectorcall + fun:_PyObject_FastCallDict +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:function_code_fastcall + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:method_vectorcall + fun:_PyObject_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_FastCallDict + fun:_PyObject_Call_Prepend + fun:slot_tp_call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:PyCode_Optimize + fun:makecode + fun:assemble + fun:compiler_comprehension + fun:compiler_listcomp + fun:compiler_visit_expr1 + fun:compiler_visit_expr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_FastCallDict + fun:_PyObject_Call_Prepend + fun:slot_tp_new +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyObject_GC_Resize + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:PyVectorcall_Call + fun:PyObject_Call + fun:bounded_lru_cache_wrapper + fun:lru_cache_call + fun:PyObject_Call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyObject_GC_Resize + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:function_code_fastcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:type_new + fun:type_call + fun:_PyObject_MakeTpCall + fun:_PyObject_FastCallDict +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyMem_Malloc + fun:new_dict_with_shared_keys + fun:_PyObjectDict_SetItem + fun:_PyObject_GenericSetAttrWithDict + fun:PyObject_GenericSetAttr + fun:PyObject_SetAttr + fun:_PyObject_SetAttrId + fun:property_init_impl + fun:property_init + fun:type_call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:PyMarshal_ReadObjectFromString +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:_PyStack_AsDict + fun:_PyObject_MakeTpCall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:symtable_add_def_helper + fun:symtable_add_def + fun:symtable_visit_params + fun:symtable_visit_arguments + fun:symtable_visit_stmt + fun:PySymtable_BuildObject +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:dict_new + fun:type_call + fun:_PyObject_MakeTpCall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:_PyEval_EvalCodeWithName +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromStringAndSize + fun:PyErr_NewException + fun:PyInit__decimal + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:mro_invoke + fun:mro_internal + fun:PyType_Ready + fun:PyStructSequence_InitType2 +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:_PyDict_NewPresized + fun:_PyStack_AsDict + fun:_PyObject_MakeTpCall + fun:_PyObject_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyCode_NewWithPosOnlyArgs + fun:makecode + fun:assemble + fun:compiler_function + fun:compiler_visit_stmt + fun:compiler_function + fun:compiler_visit_stmt + fun:compiler_body + fun:compiler_mod +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyTuple_FromArray + fun:builtin___build_class__ + fun:cfunction_vectorcall_FASTCALL_KEYWORDS + fun:_PyObject_Vectorcall + fun:call_function +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyObject_GC_Resize + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:_PyObject_FastCall + fun:object_vacall + fun:_PyObject_CallMethodIdObjArgs + fun:PyImport_ImportModuleLevelObject +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyObject_GC_Resize + fun:_PyFrame_New_NoTrack + fun:function_code_fastcall + fun:_PyFunction_Vectorcall + fun:PyVectorcall_Call + fun:PyObject_Call + fun:PyEval_CallObjectWithKeywords + fun:_PyCodec_Lookup + fun:config_get_codec_name +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:PyCode_Optimize + fun:makecode + fun:assemble + fun:compiler_function + fun:compiler_visit_stmt + fun:compiler_function + fun:compiler_visit_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyObject_GC_Resize + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:_PyEval_EvalCodeWithName +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyCode_ConstantKey + fun:merge_consts_recursive + fun:compiler_add_const + fun:compiler_addop_load_const + fun:compiler_call_helper +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:resize_compact + fun:_PyUnicodeWriter_Finish + fun:_PyUnicode_DecodeUnicodeEscape + fun:decode_unicode_with_escapes + fun:parsestr + fun:parsestrplus + fun:ast_for_atom + fun:ast_for_atom_expr + fun:ast_for_power +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyObject_GC_Resize + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:_PyEval_EvalCodeWithName +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:dictresize + fun:insertion_resize + fun:insertdict + fun:PyDict_SetItem + fun:cache_struct_converter + fun:calcsize + fun:cfunction_vectorcall_O + fun:_PyObject_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:new_dict_with_shared_keys + fun:PyObject_GenericGetDict + fun:getset_get + fun:_PyObject_GenericGetAttrWithDict + fun:_PyObject_LookupAttr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:_PyUnicode_AsASCIIString + fun:PyUnicode_AsASCIIString + fun:Struct___init___impl + fun:Struct___init__ + fun:type_call + fun:_PyObject_MakeTpCall + fun:_PyObject_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:get_latin1_char + fun:_PyUnicode_FromUCS1 + fun:PyUnicode_FromKindAndData + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:_PyObjectDict_SetItem + fun:_PyObject_GenericSetAttrWithDict + fun:PyObject_GenericSetAttr + fun:wrap_setattr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_DecodeUTF8 + fun:new_identifier + fun:ast_for_arg + fun:ast_for_arguments + fun:ast_for_funcdef_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyStack_UnpackDict + fun:_PyObject_FastCallDict + fun:_PyObject_Call_Prepend + fun:slot_tp_init + fun:type_call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:clone_combined_dict + fun:PyDict_Copy + fun:set_names + fun:type_new + fun:tp_new_wrapper + fun:cfunction_call_varargs + fun:PyCFunction_Call + fun:do_call_core + fun:_PyEval_EvalFrameDefault +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyCode_NewWithPosOnlyArgs + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyCode_NewWithPosOnlyArgs + fun:makecode + fun:assemble + fun:compiler_comprehension + fun:compiler_listcomp + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:maybe_optimize_method_call + fun:compiler_call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:PyType_GenericAlloc + fun:dict_new + fun:type_call + fun:_PyObject_MakeTpCall + fun:_PyObject_Vectorcall + fun:_PyObject_CallNoArg + fun:_PyDict_FromKeys +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyObject_GC_Resize + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:_PyEval_EvalCodeWithName +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:fold_tuple_on_constants + fun:PyCode_Optimize + fun:makecode + fun:assemble + fun:compiler_mod +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:PyModule_AddObject + fun:PyInit_itertools + fun:_imp_create_builtin +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:do_mktuple + fun:do_mkvalue + fun:do_mkstack + fun:va_build_stack + fun:_Py_VaBuildStack +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:clone_combined_dict + fun:PyDict_Copy + fun:type_new + fun:type_call + fun:_PyObject_MakeTpCall + fun:_PyObject_FastCallDict + fun:builtin___build_class__ + fun:cfunction_vectorcall_FASTCALL_KEYWORDS + fun:_PyObject_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:_PyEval_EvalCodeWithName + fun:PyEval_EvalCodeEx + fun:PyEval_EvalCode + fun:builtin_exec_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:r_object + fun:r_object + fun:read_object + fun:marshal_loads_impl + fun:marshal_loads +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:_PyObject_FastCall + fun:object_vacall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyTuple_FromArray + fun:_PyObject_MakeTpCall + fun:_PyObject_FastCallDict + fun:builtin___build_class__ + fun:cfunction_vectorcall_FASTCALL_KEYWORDS +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:_PyCode_ConstantKey + fun:merge_const_tuple + fun:makecode + fun:assemble +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:method_vectorcall + fun:PyVectorcall_Call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_FastCallDict + fun:_PyObject_Call_Prepend + fun:slot_tp_call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyObject_GC_Resize + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_FastCallDict + fun:_PyObject_Call_Prepend + fun:slot_tp_init + fun:type_call + fun:_PyObject_MakeTpCall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:_PyCode_ConstantKey + fun:merge_const_tuple + fun:makecode + fun:assemble +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:_PyCode_ConstantKey + fun:merge_const_tuple + fun:makecode + fun:assemble +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyObject_GC_Resize + fun:_PyFrame_New_NoTrack + fun:function_code_fastcall + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:_PyObject_FastCall + fun:object_vacall + fun:_PyObject_CallMethodIdObjArgs + fun:import_find_and_load +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:method_vectorcall + fun:_PyObject_FastCallDict +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyMem_Malloc + fun:new_dict_with_shared_keys + fun:PyObject_GenericGetDict + fun:getset_get + fun:_PyObject_GenericGetAttrWithDict + fun:_PyObject_LookupAttr + fun:_PyObject_LookupAttrId + fun:ast_type_reduce + fun:cfunction_vectorcall_NOARGS + fun:_PyObject_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:clone_combined_dict + fun:PyDict_Copy + fun:set_names + fun:type_new + fun:type_call + fun:_PyObject_MakeTpCall + fun:_PyObject_FastCallDict + fun:builtin___build_class__ + fun:cfunction_vectorcall_FASTCALL_KEYWORDS +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyType_GenericAlloc + fun:s_new + fun:type_call + fun:_PyObject_MakeTpCall + fun:_PyObject_Vectorcall + fun:_PyObject_FastCall + fun:object_vacall + fun:PyObject_CallFunctionObjArgs + fun:cache_struct_converter +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:call_function +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:resize_compact + fun:unicode_resize + fun:PyUnicode_Append + fun:unicode_concatenate + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:function_code_fastcall + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicode_FromUCS1 + fun:PyUnicode_FromKindAndData + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:PyMarshal_ReadObjectFromString +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:dictresize + fun:insertion_resize + fun:insertdict + fun:PyDict_SetItem + fun:PyDict_SetItemString + fun:PyModule_AddObject + fun:PyInit__thread + fun:_imp_create_builtin +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:dictresize + fun:insertion_resize + fun:insertdict + fun:PyDict_SetItem + fun:PyDict_SetItemString + fun:PyModule_AddObject + fun:PyInit_itertools + fun:_imp_create_builtin +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyObject_GC_Resize + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:method_vectorcall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:_PyCode_ConstantKey + fun:_PyCode_ConstantKey + fun:merge_const_tuple + fun:makecode +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyCode_ConstantKey + fun:merge_const_tuple + fun:makecode + fun:assemble + fun:compiler_class +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyTuple_FromArray + fun:method_vectorcall_VARARGS + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyObject_GC_Resize + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:_PyEval_EvalCodeWithName +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:_PyObjectDict_SetItem + fun:_PyObject_GenericSetAttrWithDict + fun:PyObject_GenericSetAttr + fun:wrap_setattr + fun:wrapperdescr_raw_call + fun:wrapper_call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:r_object + fun:r_object + fun:read_object + fun:marshal_loads_impl + fun:marshal_loads +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyCode_ConstantKey + fun:merge_const_tuple + fun:makecode + fun:assemble + fun:compiler_class +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:function_code_fastcall + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:_PyObject_FastCall + fun:call_unbound_noarg +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_DecodeUTF8 + fun:new_identifier + fun:ast_for_atom + fun:ast_for_atom_expr + fun:ast_for_power +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyTuple_FromArray + fun:PyList_AsTuple + fun:makecode + fun:assemble + fun:compiler_class +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:PyVectorcall_Call + fun:PyObject_Call + fun:bounded_lru_cache_wrapper +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:_PyObjectDict_SetItem + fun:_PyObject_GenericSetAttrWithDict + fun:PyObject_GenericSetAttr + fun:PyObject_SetAttr + fun:_PyObject_SetAttrId + fun:_io_FileIO___init___impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:clone_combined_dict + fun:PyDict_Copy + fun:type_new + fun:tp_new_wrapper + fun:cfunction_call_varargs + fun:PyCFunction_Call + fun:do_call_core + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyTuple_FromArray + fun:_PyObject_MakeTpCall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:_PyCode_ConstantKey + fun:merge_const_tuple + fun:makecode + fun:assemble +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:clone_combined_dict + fun:PyDict_Copy + fun:set_names + fun:type_new + fun:tp_new_wrapper +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicode_FromUCS1 + fun:PyUnicode_FromKindAndData + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:read_object +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:dictiter_iternextitem + fun:list_extend + fun:_PyList_Extend + fun:PySequence_List + fun:method_output_as_list +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyTuple_FromArray + fun:PyList_AsTuple + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:_PyEval_EvalCodeWithName +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyTuple_FromArray + fun:PyList_AsTuple + fun:PySequence_Tuple + fun:tuple_new_impl + fun:tuple_new +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyMem_Malloc + fun:prepare_s + fun:Struct___init___impl + fun:Struct___init__ + fun:type_call + fun:_PyObject_MakeTpCall + fun:_PyObject_Vectorcall + fun:_PyObject_FastCall + fun:object_vacall + fun:PyObject_CallFunctionObjArgs +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyTuple_FromArray + fun:PyList_AsTuple + fun:makecode + fun:assemble + fun:compiler_function +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:_PyEval_EvalCodeWithName + fun:PyEval_EvalCodeEx + fun:builtin___build_class__ + fun:cfunction_vectorcall_FASTCALL_KEYWORDS +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:read_object +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:dictresize + fun:insertion_resize + fun:insertdict + fun:PyDict_SetItem + fun:PyDict_SetItemString + fun:PyModule_AddObject + fun:PyInit_time + fun:_imp_create_builtin +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:dictresize + fun:insertion_resize + fun:insertdict + fun:PyDict_SetItem + fun:PyDict_SetItemString + fun:PyModule_AddObject + fun:PyInit__ctypes + fun:_PyImport_LoadDynamicModuleWithSpec +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:function_code_fastcall + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:_PyObject_FastCall + fun:_PyObject_FastCall_Prepend +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyTuple_FromArray + fun:_PyObject_MakeTpCall + fun:_PyObject_Vectorcall + fun:_PyObject_FastCall + fun:init_subclass +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyCode_ConstantKey + fun:_PyCode_ConstantKey + fun:merge_const_tuple + fun:makecode + fun:assemble +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:dict_keys_inorder + fun:makecode + fun:assemble + fun:compiler_function + fun:compiler_visit_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:parsestr + fun:parsestrplus + fun:ast_for_atom + fun:ast_for_atom_expr + fun:ast_for_power + fun:ast_for_expr + fun:ast_for_call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:function_code_fastcall + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyMem_Malloc + fun:new_dict_with_shared_keys + fun:_PyObjectDict_SetItem + fun:_PyObject_GenericSetAttrWithDict + fun:PyObject_GenericSetAttr + fun:PyObject_SetAttr + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:function_code_fastcall + fun:_PyFunction_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:parsestr + fun:parsestrplus + fun:ast_for_atom + fun:ast_for_atom_expr + fun:ast_for_power +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_FastCallDict + fun:_PyObject_Call_Prepend + fun:slot_tp_new +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:dictiter_iternextitem + fun:list_extend + fun:list___init___impl + fun:list___init__ + fun:type_call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_FastCallDict + fun:_PyObject_Call_Prepend + fun:slot_tp_init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:function_code_fastcall + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyTuple_FromArray + fun:_PyObject_MakeTpCall + fun:_PyObject_Vectorcall + fun:_PyObject_FastCall + fun:_PyEval_EvalFrameDefault +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:method_vectorcall + fun:_PyObject_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:_PyObject_FastCall + fun:_PyObject_FastCall_Prepend +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:function_code_fastcall + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:_PyObject_FastCall + fun:property_descr_get +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:dict_keys_inorder + fun:makecode + fun:assemble + fun:compiler_function + fun:compiler_visit_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:dictresize + fun:insertion_resize + fun:insertdict + fun:PyDict_SetItem + fun:_PyObjectDict_SetItem + fun:_PyObject_GenericSetAttrWithDict + fun:PyObject_GenericSetAttr + fun:PyObject_SetAttr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:function_code_fastcall + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:method_vectorcall + fun:_PyObject_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:dictresize + fun:insertion_resize + fun:insertdict + fun:PyDict_SetItem + fun:_PyObjectDict_SetItem + fun:_PyObject_GenericSetAttrWithDict + fun:PyObject_GenericSetAttr + fun:wrap_setattr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_FastCallDict + fun:_PyObject_Call_Prepend + fun:slot_tp_init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyObject_GC_Resize + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:function_code_fastcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyTuple_FromArray + fun:_PyObject_MakeTpCall + fun:_PyObject_Vectorcall + fun:_PyObject_FastCall + fun:_PyObject_CallFunctionVa +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:PyVectorcall_Call + fun:PyObject_Call + fun:do_call_core +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:dictresize + fun:insertion_resize + fun:insertdict + fun:PyDict_SetItem + fun:PyDict_SetItemString + fun:_PySys_InitMain + fun:pyinit_main + fun:Py_InitializeFromConfig +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:dictresize + fun:insertion_resize + fun:insertdict + fun:PyDict_SetItem + fun:_inscode + fun:PyInit_errno + fun:_imp_create_builtin + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:dictresize + fun:dict_merge + fun:PyDict_Update + fun:PyImport_Cleanup + fun:Py_FinalizeEx + fun:Py_RunMain + fun:pymain_main + fun:Py_BytesMain +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyCode_ConstantKey + fun:merge_const_tuple + fun:makecode + fun:assemble + fun:compiler_function +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyCode_ConstantKey + fun:merge_const_tuple + fun:makecode + fun:assemble + fun:compiler_function +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyCode_ConstantKey + fun:merge_const_tuple + fun:makecode + fun:assemble + fun:compiler_function +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:dictresize + fun:insertion_resize + fun:insertdict + fun:PyDict_SetItem + fun:dict_ass_sub + fun:PyObject_SetItem + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:new_dict_with_shared_keys + fun:_PyObjectDict_SetItem + fun:_PyObject_GenericSetAttrWithDict + fun:PyObject_GenericSetAttr + fun:PyObject_SetAttr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:int_to_bytes_impl + fun:int_to_bytes + fun:cfunction_vectorcall_FASTCALL_KEYWORDS + fun:PyVectorcall_Call + fun:PyObject_Call + fun:methodcaller_call + fun:_PyObject_MakeTpCall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:function_code_fastcall + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:_PyObject_FastCall + fun:object_vacall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:function_code_fastcall + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:method_vectorcall + fun:_PyObject_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:read_object +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyUnicode_InternFromString + fun:PyObject_SetAttrString + fun:_add_methods_to_object + fun:PyModule_AddFunctions +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:_PyUnicode_AsLatin1String + fun:PyUnicode_AsEncodedString + fun:unicode_encode_impl + fun:unicode_encode + fun:cfunction_vectorcall_FASTCALL_KEYWORDS + fun:PyVectorcall_Call + fun:PyCFunction_Call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:function_code_fastcall + fun:_PyFunction_Vectorcall + fun:_PyObject_FastCallDict + fun:_PyObject_Call_Prepend + fun:slot_tp_init +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyTuple_FromArray + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:call_function +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:_PyObjectDict_SetItem + fun:_PyObject_GenericSetAttrWithDict + fun:PyObject_GenericSetAttr + fun:PyObject_SetAttr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyObject_GC_Resize + fun:_PyFrame_New_NoTrack + fun:function_code_fastcall + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:function_code_fastcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_FastCallDict +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:clone_combined_dict + fun:PyDict_Copy + fun:set_names + fun:type_new + fun:type_call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyTuple_FromArray + fun:_PyObject_MakeTpCall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:r_object + fun:r_object + fun:r_object + fun:read_object + fun:marshal_loads_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:dictresize + fun:insertion_resize + fun:insertdict + fun:PyDict_SetItem + fun:PyDict_SetItemString + fun:PyModule_AddObject + fun:PyModule_AddIntConstant + fun:all_ins +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:read_object +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyObject_GC_Resize + fun:_PyTuple_Resize + fun:PySequence_Tuple + fun:tuple_new_impl + fun:tuple_new + fun:type_call + fun:_PyObject_MakeTpCall + fun:_PyObject_Vectorcall + fun:call_function +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicode_FromUCS1 + fun:PyUnicode_FromKindAndData + fun:r_object + fun:r_object + fun:r_object + fun:read_object + fun:marshal_loads_impl + fun:marshal_loads +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:_PyDict_NewPresized + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:_PyEval_EvalCodeWithName + fun:PyEval_EvalCodeEx + fun:PyEval_EvalCode + fun:builtin_exec_impl + fun:builtin_exec +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:_PyObjectDict_SetItem + fun:_PyObject_GenericSetAttrWithDict + fun:PyObject_GenericSetAttr + fun:PyObject_SetAttr + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:function_code_fastcall + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyCode_NewWithPosOnlyArgs + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:read_object +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicode_FromUCS1 + fun:PyUnicode_FromKindAndData + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:read_object +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:unicode_encode_utf8 + fun:_PyUnicode_AsUTF8String + fun:PyUnicode_AsEncodedString + fun:unicode_encode_impl + fun:unicode_encode + fun:cfunction_vectorcall_FASTCALL_KEYWORDS + fun:PyVectorcall_Call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:read_object +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:dictresize + fun:insertion_resize + fun:insertdict + fun:PyDict_SetItem + fun:PyDict_MergeFromSeq2 + fun:dict_update_common + fun:dict_init + fun:type_call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicode_FromUCS1 + fun:PyUnicode_FromKindAndData + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:read_object + fun:marshal_loads_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyObject_GC_Resize + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:PyVectorcall_Call + fun:PyObject_Call + fun:do_call_core + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicode_FromUCS1 + fun:PyUnicode_FromKindAndData + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:read_object +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:function_code_fastcall + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:_PyEval_EvalCodeWithName + fun:PyEval_EvalCodeEx + fun:builtin___build_class__ + fun:cfunction_vectorcall_FASTCALL_KEYWORDS +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:read_object +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyLong_New + fun:PyLong_FromLong + fun:r_object + fun:r_object + fun:r_object + fun:read_object + fun:marshal_loads_impl + fun:marshal_loads + fun:cfunction_vectorcall_O +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyFloat_FromDouble + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:read_object + fun:marshal_loads_impl + fun:marshal_loads +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:get_latin1_char + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:_PySys_InitCore + fun:_PySys_Create + fun:pyinit_config + fun:pyinit_core +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:get_latin1_char + fun:unicode_char + fun:PyUnicode_FromOrdinal + fun:unicodeiter_next + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:function_code_fastcall + fun:_PyFunction_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:get_latin1_char + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromStringAndSize + fun:do_mkvalue + fun:do_mkstack + fun:va_build_stack + fun:_Py_VaBuildStack_SizeT +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:PyModule_AddObject + fun:PyInit_math + fun:_PyImport_LoadDynamicModuleWithSpec +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:PyModule_AddObject + fun:PyInit_math + fun:_PyImport_LoadDynamicModuleWithSpec +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicode_FromUCS1 + fun:PyUnicode_FromKindAndData + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:PyMarshal_ReadObjectFromString + fun:get_frozen_object +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:PyModule_AddObject + fun:PyInit__sha512 + fun:_PyImport_LoadDynamicModuleWithSpec +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:PyModule_AddObject + fun:PyInit__sha512 + fun:_PyImport_LoadDynamicModuleWithSpec +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:func_get_annotations + fun:getset_get + fun:_PyObject_GenericGetAttrWithDict + fun:_PyObject_LookupAttr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:_PyObject_FastCall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:get_latin1_char + fun:unicode_char + fun:unicode_getitem + fun:unicode_subscript + fun:PyObject_GetItem + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:_PyEval_EvalCodeWithName +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:get_latin1_char + fun:unicode_char + fun:PyUnicode_FromOrdinal + fun:unicodeiter_next + fun:PyIter_Next + fun:set_update_internal + fun:make_new_set + fun:frozenset_new +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyMem_Malloc + fun:make_keys_shared + fun:_PyObjectDict_SetItem + fun:_PyObject_GenericSetAttrWithDict + fun:PyObject_GenericSetAttr + fun:PyObject_SetAttr + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:function_code_fastcall + fun:_PyFunction_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:add_subclass + fun:PyType_Ready + fun:type_new + fun:tp_new_wrapper + fun:cfunction_call_varargs + fun:PyCFunction_Call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyFloat_FromDouble + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:r_object + fun:read_object + fun:marshal_loads_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:dictresize + fun:insertion_resize + fun:insertdict + fun:PyDict_SetItem + fun:PyDict_SetItemString + fun:PyModule_AddObject + fun:PyInit__random + fun:_PyImport_LoadDynamicModuleWithSpec +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:dictresize + fun:insertion_resize + fun:insertdict + fun:PyDict_SetItem + fun:PyDict_SetItemString + fun:PyModule_AddObject + fun:_PyWarnings_Init + fun:_imp_create_builtin +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:symtable_add_def_helper + fun:symtable_add_def + fun:symtable_visit_params + fun:symtable_visit_arguments + fun:symtable_visit_stmt + fun:symtable_visit_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyFloat_FromDouble + fun:parsenumber_raw + fun:parsenumber + fun:ast_for_atom + fun:ast_for_atom_expr + fun:ast_for_power + fun:ast_for_expr + fun:ast_for_ifexpr + fun:ast_for_expr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyFloat_FromDouble + fun:parsenumber_raw + fun:parsenumber + fun:ast_for_atom + fun:ast_for_atom_expr + fun:ast_for_power + fun:ast_for_expr + fun:ast_for_arguments + fun:ast_for_funcdef_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyFloat_FromDouble + fun:parsenumber_raw + fun:parsenumber + fun:ast_for_atom + fun:ast_for_atom_expr + fun:ast_for_power + fun:ast_for_expr + fun:ast_for_expr + fun:ast_for_ifexpr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyFloat_FromDouble + fun:parsenumber_raw + fun:parsenumber + fun:ast_for_atom + fun:ast_for_atom_expr + fun:ast_for_power + fun:ast_for_expr + fun:ast_for_expr + fun:ast_for_if_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyLong_New + fun:PyLong_FromLong + fun:parsenumber_raw + fun:parsenumber + fun:ast_for_atom + fun:ast_for_atom_expr + fun:ast_for_power + fun:ast_for_expr + fun:ast_for_arguments +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyBytes_Resize + fun:assemble + fun:compiler_comprehension + fun:compiler_listcomp + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_return + fun:compiler_visit_stmt + fun:compiler_function +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyBytes_Resize + fun:assemble + fun:compiler_comprehension + fun:compiler_setcomp + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_call_helper + fun:compiler_call + fun:compiler_visit_expr1 +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:compiler_from_import + fun:compiler_visit_stmt + fun:compiler_body + fun:compiler_mod + fun:PyAST_CompileObject +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:__Pyx_InitCachedConstants + fun:__pyx_pymod_exec__yaml + fun:PyModule_ExecDef + fun:exec_builtin_or_dynamic + fun:_imp_exec_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:__Pyx_InitCachedConstants + fun:__pyx_pymod_exec__yaml + fun:PyModule_ExecDef + fun:exec_builtin_or_dynamic + fun:_imp_exec_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:__Pyx_InitCachedConstants + fun:__pyx_pymod_exec__yaml + fun:PyModule_ExecDef + fun:exec_builtin_or_dynamic + fun:_imp_exec_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:__Pyx_InitCachedConstants + fun:__pyx_pymod_exec__yaml + fun:PyModule_ExecDef + fun:exec_builtin_or_dynamic + fun:_imp_exec_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:__Pyx_InitCachedConstants + fun:__pyx_pymod_exec__yaml + fun:PyModule_ExecDef + fun:exec_builtin_or_dynamic + fun:_imp_exec_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:__Pyx_InitCachedConstants + fun:__pyx_pymod_exec__yaml + fun:PyModule_ExecDef + fun:exec_builtin_or_dynamic + fun:_imp_exec_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:__Pyx_InitCachedConstants + fun:__pyx_pymod_exec__yaml + fun:PyModule_ExecDef + fun:exec_builtin_or_dynamic + fun:_imp_exec_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:__Pyx_InitCachedConstants + fun:__pyx_pymod_exec__yaml + fun:PyModule_ExecDef + fun:exec_builtin_or_dynamic + fun:_imp_exec_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:__Pyx_InitCachedConstants + fun:__pyx_pymod_exec__yaml + fun:PyModule_ExecDef + fun:exec_builtin_or_dynamic + fun:_imp_exec_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:__Pyx_InitCachedConstants + fun:__pyx_pymod_exec__yaml + fun:PyModule_ExecDef + fun:exec_builtin_or_dynamic + fun:_imp_exec_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:__Pyx_InitCachedConstants + fun:__pyx_pymod_exec__yaml + fun:PyModule_ExecDef + fun:exec_builtin_or_dynamic + fun:_imp_exec_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:__Pyx_modinit_type_init_code + fun:__pyx_pymod_exec__yaml + fun:PyModule_ExecDef + fun:exec_builtin_or_dynamic +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:__Pyx_modinit_type_init_code + fun:__pyx_pymod_exec__yaml + fun:PyModule_ExecDef + fun:exec_builtin_or_dynamic +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:PyType_Ready + fun:__Pyx_modinit_type_init_code + fun:__pyx_pymod_exec__yaml + fun:PyModule_ExecDef + fun:exec_builtin_or_dynamic +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:compiler_call_helper + fun:compiler_call + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_decorators +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyFloat_FromDouble + fun:parsenumber_raw + fun:parsenumber + fun:ast_for_atom + fun:ast_for_atom_expr + fun:ast_for_power + fun:ast_for_expr + fun:ast_for_testlist + fun:ast_for_flow_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:PyModule_AddObject + fun:PyInit_unicodedata + fun:_PyImport_LoadDynamicModuleWithSpec +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:PyModule_AddObject + fun:PyInit__hashlib + fun:_PyImport_LoadDynamicModuleWithSpec +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:PyCode_Optimize + fun:makecode + fun:assemble + fun:compiler_comprehension + fun:compiler_setcomp + fun:compiler_visit_expr1 + fun:compiler_visit_expr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:PyModule_AddObject + fun:PyInit_pyexpat + fun:_PyImport_LoadDynamicModuleWithSpec +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PySequence_Tuple + fun:intern_string_constants + fun:PyCode_NewWithPosOnlyArgs + fun:r_object + fun:r_object +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyCode_ConstantKey + fun:merge_consts_recursive + fun:compiler_add_const + fun:compiler_addop_load_const + fun:compiler_subdict +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:PyModule_AddObject + fun:PyInit_unicodedata + fun:_PyImport_LoadDynamicModuleWithSpec +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:PyModule_AddObject + fun:PyInit__json + fun:_PyImport_LoadDynamicModuleWithSpec +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:PyModule_AddObject + fun:PyInit_unicodedata + fun:_PyImport_LoadDynamicModuleWithSpec +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromString + fun:PyDict_SetItemString + fun:PyCodec_RegisterError + fun:_PyCodecRegistry_Init + fun:_PyCodec_Lookup +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:ste_new + fun:symtable_enter_block + fun:symtable_visit_stmt + fun:PySymtable_BuildObject +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:analyze_block + fun:analyze_child_block + fun:analyze_block + fun:analyze_child_block +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:compiler_enter_scope + fun:compiler_comprehension + fun:compiler_genexp + fun:compiler_visit_expr1 +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:ste_new + fun:symtable_enter_block + fun:symtable_visit_stmt + fun:symtable_visit_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:ste_new + fun:symtable_enter_block + fun:symtable_handle_comprehension + fun:symtable_visit_genexp +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_DecodeUTF8 + fun:alias_for_import_name + fun:ast_for_import_stmt + fun:ast_for_stmt + fun:ast_for_suite +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:resize_compact + fun:unicode_resize + fun:PyUnicode_Append + fun:compiler_set_qualname + fun:compiler_enter_scope + fun:compiler_comprehension + fun:compiler_setcomp + fun:compiler_visit_expr1 + fun:compiler_visit_expr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:attrgetter_new + fun:type_call + fun:_PyObject_MakeTpCall + fun:_PyObject_Vectorcall + fun:call_function +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:resize_compact + fun:unicode_resize + fun:PyUnicode_Append + fun:PyUnicode_AppendAndDel + fun:FstringParser_ConcatAndDel + fun:FstringParser_ConcatFstring + fun:parsestrplus + fun:ast_for_atom + fun:ast_for_atom_expr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyCode_ConstantKey + fun:merge_consts_recursive + fun:compiler_add_const + fun:compiler_addop_load_const + fun:compiler_visit_expr1 +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:list2dict + fun:compiler_enter_scope + fun:compiler_comprehension + fun:compiler_genexp + fun:compiler_visit_expr1 + fun:compiler_visit_expr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:compiler_add_o + fun:compiler_add_const + fun:compiler_addop_load_const + fun:assemble + fun:compiler_comprehension + fun:compiler_genexp +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:_PyObjectDict_SetItem + fun:_PyObject_GenericSetAttrWithDict + fun:PyObject_GenericSetAttr + fun:PyObject_SetAttr + fun:_PyObject_SetAttrId + fun:ast2obj_expr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:compiler_add_o + fun:compiler_nameop + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_visit_stmt + fun:compiler_function +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:symtable_add_def_helper + fun:symtable_add_def + fun:symtable_implicit_arg + fun:symtable_handle_comprehension + fun:symtable_visit_setcomp + fun:symtable_visit_expr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyCode_NewWithPosOnlyArgs + fun:makecode + fun:assemble + fun:compiler_comprehension + fun:compiler_listcomp + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_return + fun:compiler_visit_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyCode_NewWithPosOnlyArgs + fun:makecode + fun:assemble + fun:compiler_comprehension + fun:compiler_setcomp + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_call_helper + fun:compiler_call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:compiler_add_o + fun:compiler_addop_name + fun:compiler_import + fun:compiler_visit_stmt + fun:compiler_body + fun:compiler_mod +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:_PyObjectDict_SetItem + fun:_PyObject_GenericSetAttrWithDict + fun:PyObject_GenericSetAttr + fun:PyObject_SetAttr + fun:_PyObject_SetAttrId + fun:ast2obj_alias +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:lru_cache_new + fun:type_call + fun:_PyObject_MakeTpCall + fun:_PyObject_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:_PyObjectDict_SetItem + fun:_PyObject_GenericSetAttrWithDict + fun:PyObject_GenericSetAttr + fun:PyObject_SetAttr + fun:_PyObject_SetAttrId + fun:ast2obj_expr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyTuple_FromArray + fun:PyList_AsTuple + fun:compiler_visit_annotations + fun:compiler_function + fun:compiler_visit_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:dictresize + fun:insertion_resize + fun:insertdict + fun:PyDict_SetItem + fun:PyDict_SetItemString + fun:PyModule_AddObject + fun:_PyImport_LoadDynamicModuleWithSpec + fun:_imp_create_dynamic_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:_PyDict_NewPresized + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:_PyEval_EvalCodeWithName + fun:PyEval_EvalCodeEx + fun:builtin___build_class__ + fun:cfunction_vectorcall_FASTCALL_KEYWORDS + fun:_PyObject_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyCode_ConstantKey + fun:merge_consts_recursive + fun:compiler_add_const + fun:compiler_addop_load_const + fun:compiler_visit_annotations +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:symtable_add_def_helper + fun:symtable_add_def + fun:symtable_implicit_arg + fun:symtable_handle_comprehension + fun:symtable_visit_listcomp + fun:symtable_visit_expr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyLong_New + fun:PyLong_FromLong + fun:long_add + fun:binary_op1 + fun:PyNumber_Add + fun:compute_range_length + fun:make_range_object + fun:range_new + fun:type_call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:dictresize + fun:dict_merge + fun:_PyDict_MergeEx + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyObject_GC_Resize + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:gen_send_ex +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:dictresize + fun:insertion_resize + fun:insertdict + fun:PyDict_SetItem + fun:PyDict_SetItemString + fun:PyModule_AddObject + fun:PyInit_unicodedata + fun:_PyImport_LoadDynamicModuleWithSpec +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_New + fun:make_range_object + fun:range_new + fun:type_call + fun:_PyObject_MakeTpCall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:parsestr + fun:parsestrplus + fun:ast_for_atom + fun:ast_for_atom_expr + fun:ast_for_power + fun:ast_for_expr + fun:seq_for_testlist +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyBytes_Resize + fun:assemble + fun:compiler_comprehension + fun:compiler_genexp + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:maybe_optimize_method_call + fun:compiler_call + fun:compiler_visit_expr1 +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyBytes_Resize + fun:assemble + fun:compiler_comprehension + fun:compiler_dictcomp + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_return + fun:compiler_visit_stmt + fun:compiler_if +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyBytes_Resize + fun:assemble + fun:compiler_comprehension + fun:compiler_dictcomp + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_return + fun:compiler_visit_stmt + fun:compiler_function +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyBytes_Resize + fun:assemble + fun:compiler_comprehension + fun:compiler_dictcomp + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_return + fun:compiler_visit_stmt + fun:compiler_if +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyBytes_Resize + fun:assemble + fun:compiler_lambda + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_visit_stmt + fun:compiler_try_except + fun:compiler_try + fun:compiler_visit_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:compiler_from_import + fun:compiler_visit_stmt + fun:compiler_try_except + fun:compiler_try + fun:compiler_visit_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:dict_keys_inorder + fun:makecode + fun:assemble + fun:compiler_lambda + fun:compiler_visit_expr1 +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyBytes_Resize + fun:assemble + fun:compiler_comprehension + fun:compiler_dictcomp + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:maybe_optimize_method_call + fun:compiler_call + fun:compiler_visit_expr1 +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:dictiter_new + fun:dictitems_iter + fun:PyObject_GetIter + fun:_PyEval_EvalFrameDefault +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:make_const_tuple + fun:fold_tuple + fun:astfold_expr + fun:astfold_stmt + fun:astfold_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:_PyCode_ConstantKey + fun:merge_consts_recursive + fun:merge_consts_recursive + fun:compiler_add_const +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:dict_keys_inorder + fun:makecode + fun:assemble + fun:compiler_comprehension + fun:compiler_setcomp +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyCode_ConstantKey + fun:_PyCode_ConstantKey + fun:merge_consts_recursive + fun:compiler_add_const + fun:compiler_addop_load_const +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyBytes_Resize + fun:assemble + fun:compiler_function + fun:compiler_visit_stmt + fun:compiler_try_except + fun:compiler_try + fun:compiler_visit_stmt + fun:compiler_function + fun:compiler_visit_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:PyCode_Optimize + fun:makecode + fun:assemble + fun:compiler_lambda + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_visit_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:compiler_init + fun:PyAST_CompileObject + fun:Py_CompileStringObject + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:dictbytype + fun:compiler_enter_scope + fun:compiler_class + fun:compiler_visit_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:dictbytype + fun:compiler_enter_scope + fun:compiler_class + fun:compiler_visit_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:dictbytype + fun:compiler_enter_scope + fun:compiler_comprehension + fun:compiler_setcomp +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:compiler_enter_scope + fun:compiler_comprehension + fun:compiler_setcomp + fun:compiler_visit_expr1 +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:compiler_enter_scope + fun:compiler_comprehension + fun:compiler_setcomp + fun:compiler_visit_expr1 +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:list2dict + fun:compiler_enter_scope + fun:compiler_lambda + fun:compiler_visit_expr1 +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:dictbytype + fun:compiler_enter_scope + fun:compiler_lambda + fun:compiler_visit_expr1 +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:compiler_enter_scope + fun:compiler_lambda + fun:compiler_visit_expr1 + fun:compiler_visit_expr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:compiler_subdict + fun:compiler_dict + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_visit_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyBytes_Resize + fun:assemble + fun:compiler_comprehension + fun:compiler_listcomp + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_visit_stmt + fun:compiler_if + fun:compiler_visit_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:PyCode_Optimize + fun:makecode + fun:assemble + fun:compiler_lambda + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_visit_keyword +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyBytes_Resize + fun:assemble + fun:compiler_comprehension + fun:compiler_dictcomp + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_visit_stmt + fun:compiler_if + fun:compiler_visit_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:PyCode_Optimize + fun:makecode + fun:assemble + fun:compiler_lambda + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:starunpack_helper +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_DecodeUTF8 + fun:alias_for_import_name + fun:ast_for_import_stmt + fun:ast_for_stmt + fun:PyAST_FromNodeObject +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyBytes_Resize + fun:assemble + fun:compiler_comprehension + fun:compiler_setcomp + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_visit_stmt + fun:compiler_function + fun:compiler_visit_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:resize_compact + fun:unicode_resize + fun:PyUnicode_Append + fun:compiler_set_qualname + fun:compiler_enter_scope + fun:compiler_function + fun:compiler_visit_stmt + fun:compiler_try_except + fun:compiler_try +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:resize_compact + fun:unicode_resize + fun:PyUnicode_Append + fun:compiler_set_qualname + fun:compiler_enter_scope + fun:compiler_lambda + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_subdict +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:dict_keys_inorder + fun:makecode + fun:assemble + fun:compiler_comprehension + fun:compiler_setcomp +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:make_const_tuple + fun:fold_tuple + fun:astfold_expr + fun:astfold_expr + fun:astfold_expr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyCode_ConstantKey + fun:_PyCode_ConstantKey + fun:_PyCode_ConstantKey + fun:merge_const_tuple + fun:makecode +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:do_mktuple + fun:do_mkvalue + fun:va_build_value + fun:Py_BuildValue + fun:symtable_record_directive +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:list2dict + fun:compiler_enter_scope + fun:compiler_comprehension + fun:compiler_genexp +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:resize_compact + fun:unicode_resize + fun:PyUnicode_Append + fun:compiler_set_qualname + fun:compiler_enter_scope + fun:compiler_lambda + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:starunpack_helper +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:dict_keys_inorder + fun:makecode + fun:assemble + fun:compiler_lambda + fun:compiler_visit_expr1 +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:compiler_call_helper + fun:compiler_call + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:starunpack_helper +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:ste_new + fun:symtable_enter_block + fun:PySymtable_BuildObject + fun:PyAST_CompileObject + fun:Py_CompileStringObject + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:analyze_name + fun:analyze_block + fun:symtable_analyze + fun:PySymtable_BuildObject + fun:PyAST_CompileObject + fun:Py_CompileStringObject +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:compiler_add_o + fun:compiler_addop_name + fun:compiler_from_import + fun:compiler_visit_stmt + fun:compiler_body + fun:compiler_mod +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:_PyCode_ConstantKey + fun:_PyCode_ConstantKey + fun:merge_consts_recursive + fun:compiler_add_const +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:PyCode_Optimize + fun:makecode + fun:assemble + fun:compiler_function + fun:compiler_visit_stmt + fun:compiler_try_except + fun:compiler_try +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyCode_NewWithPosOnlyArgs + fun:makecode + fun:assemble + fun:compiler_function + fun:compiler_visit_stmt + fun:compiler_try_except + fun:compiler_try + fun:compiler_visit_stmt + fun:compiler_function +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyCode_NewWithPosOnlyArgs + fun:makecode + fun:assemble + fun:compiler_lambda + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_visit_stmt + fun:compiler_try_except + fun:compiler_try +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyCode_NewWithPosOnlyArgs + fun:makecode + fun:assemble + fun:compiler_comprehension + fun:compiler_dictcomp + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:maybe_optimize_method_call + fun:compiler_call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyCode_NewWithPosOnlyArgs + fun:makecode + fun:assemble + fun:compiler_comprehension + fun:compiler_genexp + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:maybe_optimize_method_call + fun:compiler_call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:resize_compact + fun:unicode_resize + fun:PyUnicode_Append + fun:compiler_set_qualname + fun:compiler_enter_scope + fun:compiler_lambda + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_visit_keyword +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyTuple_FromArray + fun:PyList_AsTuple + fun:makecode + fun:assemble + fun:compiler_mod +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:mro_implementation + fun:type_mro_impl + fun:type_mro + fun:method_vectorcall_NOARGS + fun:_PyObject_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_DecodeUTF8 + fun:new_identifier + fun:ast_for_funcdef_impl + fun:ast_for_funcdef + fun:ast_for_decorated +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:analyze_name + fun:analyze_block + fun:analyze_child_block + fun:analyze_block + fun:analyze_child_block + fun:analyze_block +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyCode_NewWithPosOnlyArgs + fun:makecode + fun:assemble + fun:compiler_comprehension + fun:compiler_setcomp + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_visit_stmt + fun:compiler_function +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyCode_NewWithPosOnlyArgs + fun:makecode + fun:assemble + fun:compiler_lambda + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_visit_keyword + fun:compiler_call_helper + fun:compiler_call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyCode_NewWithPosOnlyArgs + fun:makecode + fun:assemble + fun:compiler_comprehension + fun:compiler_dictcomp + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_visit_stmt + fun:compiler_if +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyCode_NewWithPosOnlyArgs + fun:makecode + fun:assemble + fun:compiler_comprehension + fun:compiler_listcomp + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_visit_stmt + fun:compiler_if +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyCode_NewWithPosOnlyArgs + fun:makecode + fun:assemble + fun:compiler_lambda + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:starunpack_helper + fun:compiler_list + fun:compiler_visit_expr1 +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:PyCode_Optimize + fun:makecode + fun:assemble + fun:compiler_lambda + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_subdict +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:make_const_tuple + fun:fold_tuple + fun:astfold_expr + fun:astfold_expr + fun:astfold_keyword +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyCode_NewWithPosOnlyArgs + fun:makecode + fun:assemble + fun:compiler_comprehension + fun:compiler_dictcomp + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_return + fun:compiler_visit_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyTuple_FromArray + fun:PyList_AsTuple + fun:makecode + fun:assemble + fun:compiler_lambda +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:dict_keys_inorder + fun:makecode + fun:assemble + fun:compiler_lambda + fun:compiler_visit_expr1 +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyCode_NewWithPosOnlyArgs + fun:makecode + fun:assemble + fun:compiler_lambda + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_subdict + fun:compiler_dict + fun:compiler_visit_expr1 +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:PyCode_Optimize + fun:makecode + fun:assemble + fun:compiler_lambda + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_call_helper +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:resize_compact + fun:unicode_resize + fun:PyUnicode_Append + fun:compiler_set_qualname + fun:compiler_enter_scope + fun:compiler_lambda + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_call_helper +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyCode_NewWithPosOnlyArgs + fun:makecode + fun:assemble + fun:compiler_lambda + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_call_helper + fun:compiler_call + fun:compiler_visit_expr1 +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:compiler_enter_scope + fun:compiler_function + fun:compiler_visit_stmt + fun:compiler_function +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:_PyUnicode_DecodeUnicodeEscape + fun:decode_unicode_with_escapes + fun:fstring_find_literal + fun:fstring_find_literal_and_expr + fun:FstringParser_ConcatFstring + fun:parsestrplus + fun:ast_for_atom +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:clone_combined_dict + fun:PyDict_Copy + fun:dict_copy + fun:method_vectorcall_NOARGS + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:function_code_fastcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:analyze_name + fun:analyze_block + fun:symtable_analyze + fun:PySymtable_BuildObject + fun:PyAST_CompileObject + fun:builtin_compile_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:method_vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:new_keys_object + fun:dictresize + fun:insertion_resize + fun:insertdict + fun:PyDict_SetItem + fun:PyDict_SetItemString + fun:PyModule_AddObject + fun:PyInit__greenlet + fun:_PyImport_LoadDynamicModuleWithSpec +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyLong_New + fun:PyLong_FromLong + fun:parsenumber_raw + fun:parsenumber + fun:ast_for_atom + fun:ast_for_atom_expr + fun:ast_for_power + fun:ast_for_expr + fun:ast_for_binop +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyLong_New + fun:PyLong_FromLong + fun:parsenumber_raw + fun:parsenumber + fun:ast_for_atom + fun:ast_for_atom_expr + fun:ast_for_power + fun:ast_for_expr + fun:ast_for_binop +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:do_mkvalue + fun:do_mkstack + fun:va_build_stack + fun:_Py_VaBuildStack + fun:_PyObject_CallFunctionVa + fun:callmethod + fun:_PyObject_CallMethodId +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:tracemalloc_realloc + fun:tracemalloc_realloc_gil + fun:PyObject_Realloc + fun:_PyBytes_Resize + fun:assemble + fun:compiler_comprehension + fun:compiler_listcomp + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_return +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyMem_Malloc + fun:new_dict_with_shared_keys + fun:_PyObjectDict_SetItem + fun:_PyObject_GenericSetAttrWithDict + fun:PyObject_GenericSetAttr + fun:wrap_setattr + fun:wrapperdescr_raw_call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:dict_keys_inorder + fun:makecode +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:unicode_encode_utf8 + fun:_PyUnicode_AsUTF8String + fun:PyUnicode_AsEncodedString + fun:unicode_encode_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:tracemalloc_realloc + fun:tracemalloc_realloc_gil + fun:PyObject_Realloc + fun:_PyBytes_Resize + fun:_PyBytesWriter_Finish + fun:PyBytes_FromFormatV + fun:PyBytes_FromFormat + fun:os_putenv_impl + fun:os_putenv + fun:cfunction_vectorcall_FASTCALL +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:PyList_New + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:analyze_block + fun:analyze_child_block + fun:analyze_block + fun:symtable_analyze +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:PyType_GenericAlloc + fun:dict_new + fun:type_call + fun:_PyObject_MakeTpCall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:_PyDict_NewPresized +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:do_mktuple + fun:va_build_value +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:PyObject_GenericGetDict +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:compiler_enter_scope +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:dict_keys_inorder + fun:makecode +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicode_FromUCS1 + fun:PyUnicode_FromKindAndData + fun:r_object + fun:r_object + fun:r_object +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:tracemalloc_realloc + fun:tracemalloc_realloc_gil + fun:PyObject_Realloc + fun:resize_compact + fun:_PyUnicodeWriter_Finish + fun:build_string + fun:do_string_format + fun:method_vectorcall_VARARGS_KEYWORDS + fun:_PyObject_Vectorcall + fun:call_function +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:resize_compact + fun:_PyUnicodeWriter_Finish + fun:build_string + fun:do_string_format + fun:method_vectorcall_VARARGS_KEYWORDS + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:tracemalloc_realloc + fun:tracemalloc_realloc_gil + fun:PyObject_Realloc + fun:resize_compact + fun:unicode_resize + fun:PyUnicode_Append + fun:compiler_set_qualname + fun:compiler_enter_scope + fun:compiler_comprehension + fun:compiler_listcomp +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:compiler_call_helper + fun:compiler_call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:tracemalloc_realloc + fun:tracemalloc_realloc_gil + fun:PyObject_Realloc + fun:_PyBytes_Resize + fun:assemble + fun:compiler_comprehension + fun:compiler_dictcomp + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_visit_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:clone_combined_dict + fun:PyDict_Copy + fun:set_names + fun:type_new + fun:type_call + fun:_PyObject_MakeTpCall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:symtable_add_def_helper + fun:symtable_add_def + fun:symtable_visit_expr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:PyCode_NewWithPosOnlyArgs + fun:makecode + fun:assemble + fun:compiler_comprehension + fun:compiler_listcomp + fun:compiler_visit_expr1 +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:ste_new +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyMem_Malloc + fun:new_dict_with_shared_keys + fun:_PyObjectDict_SetItem + fun:_PyObject_GenericSetAttrWithDict + fun:PyObject_GenericSetAttr + fun:PyObject_SetAttr + fun:_PyEval_EvalFrameDefault +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:tracemalloc_realloc + fun:tracemalloc_realloc_gil + fun:PyObject_Realloc + fun:resize_compact + fun:unicode_resize + fun:PyUnicode_Append + fun:compiler_set_qualname + fun:compiler_enter_scope + fun:compiler_comprehension + fun:compiler_dictcomp +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:PyCode_Optimize + fun:makecode + fun:assemble + fun:compiler_comprehension +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyTuple_FromArray + fun:PyList_AsTuple +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:new_keys_object + fun:dictresize + fun:insertion_resize + fun:insertdict + fun:PyDict_SetItem + fun:dict_ass_sub +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicode_JoinArray + fun:PyUnicode_Join + fun:unicode_join + fun:method_vectorcall_O + fun:_PyObject_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:_PyObjectDict_SetItem + fun:_PyObject_GenericSetAttrWithDict + fun:PyObject_GenericSetAttr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:clone_combined_dict + fun:PyDict_Copy + fun:type_new + fun:type_call + fun:_PyObject_MakeTpCall + fun:_PyObject_FastCallDict +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:compiler_from_import + fun:compiler_visit_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromStringAndSize + fun:PyErr_NewException +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:tracemalloc_realloc + fun:tracemalloc_realloc_gil + fun:PyObject_Realloc + fun:_PyObject_GC_Resize + fun:_PyFrame_New_NoTrack + fun:function_code_fastcall + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:tracemalloc_realloc + fun:tracemalloc_realloc_gil + fun:PyObject_Realloc + fun:_PyObject_GC_Resize + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:_PyCode_ConstantKey +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:PyCode_NewWithPosOnlyArgs + fun:makecode + fun:assemble + fun:compiler_comprehension + fun:compiler_dictcomp + fun:compiler_visit_expr1 +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:tracemalloc_realloc + fun:tracemalloc_realloc_gil + fun:PyObject_Realloc + fun:_PyObject_GC_Resize + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyObject_GC_Resize + fun:_PyFrame_New_NoTrack + fun:function_code_fastcall + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:method_vectorcall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:tracemalloc_realloc + fun:tracemalloc_realloc_gil + fun:PyObject_Realloc + fun:_PyObject_GC_Resize + fun:_PyTuple_Resize + fun:PySequence_Tuple + fun:tuple_new_impl + fun:tuple_new + fun:type_call + fun:_PyObject_MakeTpCall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:symtable_add_def_helper + fun:symtable_add_def + fun:symtable_implicit_arg +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:dict_keys_inorder + fun:makecode +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:new_dict_with_shared_keys + fun:_PyObjectDict_SetItem +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_DecodeUTF8 + fun:new_identifier +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:tracemalloc_realloc + fun:tracemalloc_realloc_gil + fun:PyObject_Realloc + fun:resize_compact + fun:unicode_resize + fun:PyUnicode_Append + fun:unicode_concatenate + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:function_code_fastcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyCode_ConstantKey + fun:merge_consts_recursive +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:new_keys_object + fun:dictresize + fun:insertion_resize + fun:insertdict + fun:PyDict_SetItem + fun:_PyObjectDict_SetItem +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:tracemalloc_realloc + fun:tracemalloc_realloc_gil + fun:PyObject_Realloc + fun:_PyObject_GC_Resize + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:tracemalloc_realloc + fun:tracemalloc_realloc_gil + fun:PyObject_Realloc + fun:_PyObject_GC_Resize + fun:_PyFrame_New_NoTrack + fun:function_code_fastcall + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:tracemalloc_realloc + fun:tracemalloc_realloc_gil + fun:PyObject_Realloc + fun:_PyObject_GC_Resize + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:method_vectorcall + fun:PyVectorcall_Call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:clone_combined_dict + fun:PyDict_Copy + fun:type_new + fun:type_call + fun:_PyObject_MakeTpCall + fun:_PyObject_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:parsestr + fun:parsestrplus +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:tracemalloc_realloc + fun:tracemalloc_realloc_gil + fun:PyObject_Realloc + fun:resize_compact + fun:unicode_resize + fun:PyUnicode_Append + fun:PyUnicode_AppendAndDel + fun:FstringParser_ConcatAndDel + fun:parsestrplus + fun:ast_for_atom +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:_PyObjectDict_SetItem +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyCode_ConstantKey + fun:_PyCode_ConstantKey +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:symtable_add_def_helper + fun:symtable_add_def + fun:symtable_visit_params +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:function_code_fastcall + fun:_PyFunction_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyCode_ConstantKey + fun:merge_const_tuple +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:new_keys_object + fun:dictresize + fun:dict_merge + fun:PyDict_Update + fun:PyImport_Cleanup + fun:Py_FinalizeEx +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:tracemalloc_realloc + fun:tracemalloc_realloc_gil + fun:PyObject_Realloc + fun:_PyObject_GC_Resize + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:PyVectorcall_Call + fun:PyObject_Call + fun:do_call_core +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyLong_New + fun:PyLong_FromLong + fun:parsenumber_raw + fun:parsenumber + fun:ast_for_atom + fun:ast_for_atom_expr + fun:ast_for_power + fun:ast_for_expr + fun:ast_for_binop +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyLong_New + fun:PyLong_FromLong + fun:parsenumber_raw + fun:parsenumber + fun:ast_for_atom + fun:ast_for_atom_expr + fun:ast_for_power + fun:ast_for_expr + fun:ast_for_binop +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:do_mkvalue + fun:do_mkstack + fun:va_build_stack + fun:_Py_VaBuildStack + fun:_PyObject_CallFunctionVa + fun:callmethod + fun:_PyObject_CallMethodId +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:tracemalloc_realloc + fun:tracemalloc_realloc_gil + fun:PyObject_Realloc + fun:_PyBytes_Resize + fun:assemble + fun:compiler_comprehension + fun:compiler_listcomp + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_return +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyMem_Malloc + fun:new_dict_with_shared_keys + fun:_PyObjectDict_SetItem + fun:_PyObject_GenericSetAttrWithDict + fun:PyObject_GenericSetAttr + fun:wrap_setattr + fun:wrapperdescr_raw_call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:dict_keys_inorder + fun:makecode +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:unicode_encode_utf8 + fun:_PyUnicode_AsUTF8String + fun:PyUnicode_AsEncodedString + fun:unicode_encode_impl +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:tracemalloc_realloc + fun:tracemalloc_realloc_gil + fun:PyObject_Realloc + fun:_PyBytes_Resize + fun:_PyBytesWriter_Finish + fun:PyBytes_FromFormatV + fun:PyBytes_FromFormat + fun:os_putenv_impl + fun:os_putenv + fun:cfunction_vectorcall_FASTCALL +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:PyList_New + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:analyze_block + fun:analyze_child_block + fun:analyze_block + fun:symtable_analyze +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:PyType_GenericAlloc + fun:dict_new + fun:type_call + fun:_PyObject_MakeTpCall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:_PyDict_NewPresized +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:do_mktuple + fun:va_build_value +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:PyObject_GenericGetDict +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:compiler_enter_scope +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:dict_keys_inorder + fun:makecode +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicode_FromUCS1 + fun:PyUnicode_FromKindAndData + fun:r_object + fun:r_object + fun:r_object +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:tracemalloc_realloc + fun:tracemalloc_realloc_gil + fun:PyObject_Realloc + fun:resize_compact + fun:_PyUnicodeWriter_Finish + fun:build_string + fun:do_string_format + fun:method_vectorcall_VARARGS_KEYWORDS + fun:_PyObject_Vectorcall + fun:call_function +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:resize_compact + fun:_PyUnicodeWriter_Finish + fun:build_string + fun:do_string_format + fun:method_vectorcall_VARARGS_KEYWORDS + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:tracemalloc_realloc + fun:tracemalloc_realloc_gil + fun:PyObject_Realloc + fun:resize_compact + fun:unicode_resize + fun:PyUnicode_Append + fun:compiler_set_qualname + fun:compiler_enter_scope + fun:compiler_comprehension + fun:compiler_listcomp +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:compiler_call_helper + fun:compiler_call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:tracemalloc_realloc + fun:tracemalloc_realloc_gil + fun:PyObject_Realloc + fun:_PyBytes_Resize + fun:assemble + fun:compiler_comprehension + fun:compiler_dictcomp + fun:compiler_visit_expr1 + fun:compiler_visit_expr + fun:compiler_visit_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:clone_combined_dict + fun:PyDict_Copy + fun:set_names + fun:type_new + fun:type_call + fun:_PyObject_MakeTpCall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:symtable_add_def_helper + fun:symtable_add_def + fun:symtable_visit_expr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:PyCode_NewWithPosOnlyArgs + fun:makecode + fun:assemble + fun:compiler_comprehension + fun:compiler_listcomp + fun:compiler_visit_expr1 +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:ste_new +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyMem_Malloc + fun:new_dict_with_shared_keys + fun:_PyObjectDict_SetItem + fun:_PyObject_GenericSetAttrWithDict + fun:PyObject_GenericSetAttr + fun:PyObject_SetAttr + fun:_PyEval_EvalFrameDefault +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:tracemalloc_realloc + fun:tracemalloc_realloc_gil + fun:PyObject_Realloc + fun:resize_compact + fun:unicode_resize + fun:PyUnicode_Append + fun:compiler_set_qualname + fun:compiler_enter_scope + fun:compiler_comprehension + fun:compiler_dictcomp +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyBytes_FromSize + fun:PyBytes_FromStringAndSize + fun:PyCode_Optimize + fun:makecode + fun:assemble + fun:compiler_comprehension +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyTuple_FromArray + fun:PyList_AsTuple +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:new_keys_object + fun:dictresize + fun:insertion_resize + fun:insertdict + fun:PyDict_SetItem + fun:dict_ass_sub +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicode_JoinArray + fun:PyUnicode_Join + fun:unicode_join + fun:method_vectorcall_O + fun:_PyObject_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:_PyObjectDict_SetItem + fun:_PyObject_GenericSetAttrWithDict + fun:PyObject_GenericSetAttr +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:clone_combined_dict + fun:PyDict_Copy + fun:type_new + fun:type_call + fun:_PyObject_MakeTpCall + fun:_PyObject_FastCallDict +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:compiler_from_import + fun:compiler_visit_stmt +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_FromStringAndSize + fun:PyErr_NewException +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:tracemalloc_realloc + fun:tracemalloc_realloc_gil + fun:PyObject_Realloc + fun:_PyObject_GC_Resize + fun:_PyFrame_New_NoTrack + fun:function_code_fastcall + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:tracemalloc_realloc + fun:tracemalloc_realloc_gil + fun:PyObject_Realloc + fun:_PyObject_GC_Resize + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:PyTuple_Pack + fun:_PyCode_ConstantKey +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:PyCode_NewWithPosOnlyArgs + fun:makecode + fun:assemble + fun:compiler_comprehension + fun:compiler_dictcomp + fun:compiler_visit_expr1 +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:tracemalloc_realloc + fun:tracemalloc_realloc_gil + fun:PyObject_Realloc + fun:_PyObject_GC_Resize + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:PyObject_Realloc + fun:_PyObject_GC_Resize + fun:_PyFrame_New_NoTrack + fun:function_code_fastcall + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:method_vectorcall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:tracemalloc_realloc + fun:tracemalloc_realloc_gil + fun:PyObject_Realloc + fun:_PyObject_GC_Resize + fun:_PyTuple_Resize + fun:PySequence_Tuple + fun:tuple_new_impl + fun:tuple_new + fun:type_call + fun:_PyObject_MakeTpCall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:symtable_add_def_helper + fun:symtable_add_def + fun:symtable_implicit_arg +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:dict_keys_inorder + fun:makecode +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:new_dict_with_shared_keys + fun:_PyObjectDict_SetItem +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:PyUnicode_DecodeUTF8 + fun:new_identifier +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:tracemalloc_realloc + fun:tracemalloc_realloc_gil + fun:PyObject_Realloc + fun:resize_compact + fun:unicode_resize + fun:PyUnicode_Append + fun:unicode_concatenate + fun:_PyEval_EvalFrameDefault + fun:PyEval_EvalFrameEx + fun:function_code_fastcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyCode_ConstantKey + fun:merge_consts_recursive +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:new_keys_object + fun:dictresize + fun:insertion_resize + fun:insertdict + fun:PyDict_SetItem + fun:_PyObjectDict_SetItem +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:tracemalloc_realloc + fun:tracemalloc_realloc_gil + fun:PyObject_Realloc + fun:_PyObject_GC_Resize + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:tracemalloc_realloc + fun:tracemalloc_realloc_gil + fun:PyObject_Realloc + fun:_PyObject_GC_Resize + fun:_PyFrame_New_NoTrack + fun:function_code_fastcall + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:call_function + fun:_PyEval_EvalFrameDefault +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:tracemalloc_realloc + fun:tracemalloc_realloc_gil + fun:PyObject_Realloc + fun:_PyObject_GC_Resize + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:_PyObject_Vectorcall + fun:method_vectorcall + fun:PyVectorcall_Call +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:clone_combined_dict + fun:PyDict_Copy + fun:type_new + fun:type_call + fun:_PyObject_MakeTpCall + fun:_PyObject_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:PyUnicode_New + fun:_PyUnicodeWriter_PrepareInternal + fun:unicode_decode_utf8 + fun:PyUnicode_DecodeUTF8Stateful + fun:parsestr + fun:parsestrplus +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:tracemalloc_realloc + fun:tracemalloc_realloc_gil + fun:PyObject_Realloc + fun:resize_compact + fun:unicode_resize + fun:PyUnicode_Append + fun:PyUnicode_AppendAndDel + fun:FstringParser_ConcatAndDel + fun:parsestrplus + fun:ast_for_atom +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_New + fun:new_dict + fun:PyDict_New + fun:_PyObjectDict_SetItem +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyCode_ConstantKey + fun:_PyCode_ConstantKey +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:new_keys_object + fun:insert_to_emptydict + fun:PyDict_SetItem + fun:symtable_add_def_helper + fun:symtable_add_def + fun:symtable_visit_params +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:function_code_fastcall + fun:_PyFunction_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:PyTuple_New + fun:_PyCode_ConstantKey + fun:merge_const_tuple +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:new_keys_object + fun:dictresize + fun:dict_merge + fun:PyDict_Update + fun:PyImport_Cleanup + fun:Py_FinalizeEx +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:malloc + fun:_PyMem_RawMalloc + fun:tracemalloc_alloc + fun:tracemalloc_alloc_gil + fun:tracemalloc_malloc_gil + fun:PyObject_Malloc + fun:_PyObject_GC_Alloc + fun:_PyObject_GC_Malloc + fun:_PyObject_GC_NewVar + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall +} +{ + + Memcheck:Leak + match-leak-kinds: possible + fun:realloc + fun:_PyMem_RawRealloc + fun:tracemalloc_realloc + fun:tracemalloc_realloc_gil + fun:PyObject_Realloc + fun:_PyObject_GC_Resize + fun:_PyFrame_New_NoTrack + fun:_PyEval_EvalCodeWithName + fun:_PyFunction_Vectorcall + fun:PyVectorcall_Call + fun:PyObject_Call + fun:do_call_core +} \ No newline at end of file From f251e5bcca6c70a287f136c711ca43694fb1f0e4 Mon Sep 17 00:00:00 2001 From: Juanjo Alvarez Martinez Date: Thu, 23 May 2024 10:30:46 +0200 Subject: [PATCH 102/104] chore(asm): avoid double calls on some aspect front-ends (#9344) ## Description Some aspects (index, slice, ljust, format, encode, decode and replace) were calling the original function and then potentially the native aspect, which would call again the original function. This reworks these aspect front-ends so only one call is ever done, which should improve performance a little. This refactor also arised and error where we were not checking the CPython error with the new `has_pyerror()` helper in the native `api_aspect_index` so it fixed that too. ## Checklist - [X] Change(s) are motivated and described in the PR description - [X] Testing strategy is described if automated tests are not included in the PR - [X] Risks are described (performance impact, potential for breakage, maintainability) - [X] Change is maintainable (easy to change, telemetry, documentation) - [X] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [X] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [X] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [X] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --------- Signed-off-by: Juanjo Alvarez Co-authored-by: Alberto Vara --- .../_taint_tracking/Aspects/AspectIndex.cpp | 4 + .../appsec/_iast/_taint_tracking/aspects.py | 116 ++++++++---------- .../iast/aspects/test_encode_decode_aspect.py | 26 +++- .../aspects/test_index_aspect_fixtures.py | 5 +- tests/appsec/iast/aspects/test_str_aspect.py | 6 +- 5 files changed, 83 insertions(+), 74 deletions(-) diff --git a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectIndex.cpp b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectIndex.cpp index 1b03ce01eaa..88e4b522ff8 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectIndex.cpp +++ b/ddtrace/appsec/_iast/_taint_tracking/Aspects/AspectIndex.cpp @@ -1,4 +1,5 @@ #include "AspectIndex.h" +#include "Helpers.h" /** * @brief Index aspect @@ -49,6 +50,9 @@ api_index_aspect(PyObject* self, PyObject* const* args, const Py_ssize_t nargs) PyObject* idx = args[1]; PyObject* result_o = PyObject_GetItem(candidate_text, idx); + if (has_pyerr()) { + return nullptr; + } if (not ctx_map or ctx_map->empty()) { return result_o; diff --git a/ddtrace/appsec/_iast/_taint_tracking/aspects.py b/ddtrace/appsec/_iast/_taint_tracking/aspects.py index 49e50dca773..2a887f23f33 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/aspects.py +++ b/ddtrace/appsec/_iast/_taint_tracking/aspects.py @@ -210,16 +210,13 @@ def join_aspect(orig_function: Optional[Callable], flag_added_args: int, *args: def index_aspect(candidate_text: Text, index: int) -> Text: - result = candidate_text[index] + if isinstance(candidate_text, IAST.TEXT_TYPES) and isinstance(index, int): + try: + return _index_aspect(candidate_text, index) + except Exception as e: + iast_taint_log_error("IAST propagation error. index_aspect. {}".format(e)) - if not isinstance(candidate_text, IAST.TEXT_TYPES) or not isinstance(index, int): - return result - - try: - return _index_aspect(candidate_text, index) - except Exception as e: - iast_taint_log_error("IAST propagation error. index_aspect. {}".format(e)) - return result + return candidate_text[index] def slice_aspect(candidate_text: Text, start: int, stop: int, step: int) -> Text: @@ -230,15 +227,12 @@ def slice_aspect(candidate_text: Text, start: int, stop: int, step: int) -> Text or (step is not None and not isinstance(step, int)) ): return candidate_text[start:stop:step] - result = candidate_text[start:stop:step] + try: - new_result = _slice_aspect(candidate_text, start, stop, step) - if new_result != result: - raise Exception("Propagation result %r is different to candidate_text[slice] %r" % (new_result, result)) - return new_result + return _slice_aspect(candidate_text, start, stop, step) except Exception as e: iast_taint_log_error("IAST propagation error. slice_aspect. {}".format(e)) - return result + return candidate_text[start:stop:step] def bytearray_extend_aspect(orig_function: Optional[Callable], flag_added_args: int, *args: Any, **kwargs: Any) -> Any: @@ -319,29 +313,25 @@ def ljust_aspect( candidate_text = args[0] args = args[flag_added_args:] - result = candidate_text.ljust(*args, **kwargs) - - if not isinstance(candidate_text, IAST.TEXT_TYPES): - return result - - try: - ranges_new = get_ranges(candidate_text) - fillchar = parse_params(1, "fillchar", " ", *args, **kwargs) - fillchar_ranges = get_ranges(fillchar) - if ranges_new is None or (not ranges_new and not fillchar_ranges): + if isinstance(candidate_text, IAST.TEXT_TYPES): + try: + ranges_new = get_ranges(candidate_text) + fillchar = parse_params(1, "fillchar", " ", *args, **kwargs) + fillchar_ranges = get_ranges(fillchar) + if ranges_new is None or (not ranges_new and not fillchar_ranges): + return candidate_text.ljust(*args, **kwargs) + + if fillchar_ranges: + # Can only be one char, so we create one range to cover from the start to the end + ranges_new = ranges_new + [shift_taint_range(fillchar_ranges[0], len(candidate_text))] + + result = candidate_text.ljust(parse_params(0, "width", None, *args, **kwargs), fillchar) + taint_pyobject_with_ranges(result, ranges_new) return result + except Exception as e: + iast_taint_log_error("IAST propagation error. ljust_aspect. {}".format(e)) - if fillchar_ranges: - # Can only be one char, so we create one range to cover from the start to the end - ranges_new = ranges_new + [shift_taint_range(fillchar_ranges[0], len(candidate_text))] - - new_result = candidate_text.ljust(parse_params(0, "width", None, *args, **kwargs), fillchar) - taint_pyobject_with_ranges(new_result, ranges_new) - return new_result - except Exception as e: - iast_taint_log_error("IAST propagation error. ljust_aspect. {}".format(e)) - - return result + return candidate_text.ljust(*args, **kwargs) def zfill_aspect( @@ -410,16 +400,14 @@ def format_aspect( if not isinstance(candidate_text, IAST.TEXT_TYPES): return result - try: - params = tuple(args) + tuple(kwargs.values()) - new_result = _format_aspect(candidate_text, params, *args, **kwargs) - if new_result != result: - raise Exception("Propagation result %r is different to candidate_text.format %r" % (new_result, result)) - return new_result - except Exception as e: - iast_taint_log_error("IAST propagation error. format_aspect. {}".format(e)) + if isinstance(candidate_text, IAST.TEXT_TYPES): + try: + params = tuple(args) + tuple(kwargs.values()) + return _format_aspect(candidate_text, params, *args, **kwargs) + except Exception as e: + iast_taint_log_error("IAST propagation error. format_aspect. {}".format(e)) - return result + return candidate_text.format(*args, **kwargs) def format_map_aspect( @@ -684,18 +672,15 @@ def decode_aspect( self = args[0] args = args[(flag_added_args or 1) :] # Assume we call decode method of the first argument - result = self.decode(*args, **kwargs) - if not is_pyobject_tainted(self) or not isinstance(self, bytes): - return result - - try: - codec = args[0] if args else "utf-8" - inc_dec = codecs.getincrementaldecoder(codec)(**kwargs) - return incremental_translation(self, inc_dec, inc_dec.decode, "") - except Exception as e: - iast_taint_log_error("IAST propagation error. decode_aspect. {}".format(e)) - return result + if is_pyobject_tainted(self) and isinstance(self, bytes): + try: + codec = args[0] if args else "utf-8" + inc_dec = codecs.getincrementaldecoder(codec)(**kwargs) + return incremental_translation(self, inc_dec, inc_dec.decode, "") + except Exception as e: + iast_taint_log_error("IAST propagation error. decode_aspect. {}".format(e)) + return self.decode(*args, **kwargs) def encode_aspect( @@ -708,18 +693,15 @@ def encode_aspect( self = args[0] args = args[(flag_added_args or 1) :] - # Assume we call encode method of the first argument - result = self.encode(*args, **kwargs) - - if not is_pyobject_tainted(self) or not isinstance(self, str): - return result - try: - codec = args[0] if args else "utf-8" - inc_enc = codecs.getincrementalencoder(codec)(**kwargs) - return incremental_translation(self, inc_enc, inc_enc.encode, b"") - except Exception as e: - iast_taint_log_error("IAST propagation error. encode_aspect. {}".format(e)) + if is_pyobject_tainted(self) and isinstance(self, str): + try: + codec = args[0] if args else "utf-8" + inc_enc = codecs.getincrementalencoder(codec)(**kwargs) + return incremental_translation(self, inc_enc, inc_enc.encode, b"") + except Exception as e: + iast_taint_log_error("IAST propagation error. encode_aspect. {}".format(e)) + result = self.encode(*args, **kwargs) return result diff --git a/tests/appsec/iast/aspects/test_encode_decode_aspect.py b/tests/appsec/iast/aspects/test_encode_decode_aspect.py index bf7477a4fca..29161e4594d 100644 --- a/tests/appsec/iast/aspects/test_encode_decode_aspect.py +++ b/tests/appsec/iast/aspects/test_encode_decode_aspect.py @@ -131,7 +131,7 @@ def test_encode_and_add_aspect(infix, args, kwargs, should_be_tainted, prefix, s assert list_ranges[0].length == len_infix -def test_encode_error_and_no_log_metric(telemetry_writer): +def test_encode_error_with_tainted_gives_one_log_metric(telemetry_writer): string_input = taint_pyobject( pyobject="abcde", source_name="test_add_aspect_tainting_left_hand", @@ -141,11 +141,22 @@ def test_encode_error_and_no_log_metric(telemetry_writer): with pytest.raises(LookupError): mod.do_encode(string_input, "encoding-not-exists") + list_metrics_logs = list(telemetry_writer._logs) + assert len(list_metrics_logs) == 1 + assert "message" in list_metrics_logs[0] + assert "IAST propagation error. encode_aspect. " in list_metrics_logs[0]["message"] + + +def test_encode_error_with_not_tainted_gives_no_log_metric(telemetry_writer): + string_input = "abcde" + with pytest.raises(LookupError): + mod.do_encode(string_input, "encoding-not-exists") + list_metrics_logs = list(telemetry_writer._logs) assert len(list_metrics_logs) == 0 -def test_dencode_error_and_no_log_metric(telemetry_writer): +def test_dencode_error_with_tainted_gives_one_log_metric(telemetry_writer): string_input = taint_pyobject( pyobject=b"abcde", source_name="test_add_aspect_tainting_left_hand", @@ -155,5 +166,16 @@ def test_dencode_error_and_no_log_metric(telemetry_writer): with pytest.raises(LookupError): mod.do_decode(string_input, "decoding-not-exists") + list_metrics_logs = list(telemetry_writer._logs) + assert len(list_metrics_logs) == 1 + assert "message" in list_metrics_logs[0] + assert "IAST propagation error. decode_aspect. " in list_metrics_logs[0]["message"] + + +def test_dencode_error_with_not_tainted_gives_no_log_metric(telemetry_writer): + string_input = b"abcde" + with pytest.raises(LookupError): + mod.do_decode(string_input, "decoding-not-exists") + list_metrics_logs = list(telemetry_writer._logs) assert len(list_metrics_logs) == 0 diff --git a/tests/appsec/iast/aspects/test_index_aspect_fixtures.py b/tests/appsec/iast/aspects/test_index_aspect_fixtures.py index 5b6671958b9..d3f486e5b12 100644 --- a/tests/appsec/iast/aspects/test_index_aspect_fixtures.py +++ b/tests/appsec/iast/aspects/test_index_aspect_fixtures.py @@ -64,7 +64,7 @@ def test_string_index(input_str, index_pos, expected_result, tainted): @pytest.mark.skipif(sys.version_info < (3, 9, 0), reason="Python version not supported by IAST") -def test_index_error_and_no_log_metric(telemetry_writer): +def test_index_error_with_tainted_gives_one_log_metric(telemetry_writer): string_input = taint_pyobject( pyobject="abcde", source_name="test_add_aspect_tainting_left_hand", @@ -75,7 +75,8 @@ def test_index_error_and_no_log_metric(telemetry_writer): mod.do_index(string_input, 100) list_metrics_logs = list(telemetry_writer._logs) - assert len(list_metrics_logs) == 0 + assert len(list_metrics_logs) == 1 + assert "IAST propagation error. index_aspect" in list_metrics_logs[0]["message"] @pytest.mark.skip_iast_check_logs diff --git a/tests/appsec/iast/aspects/test_str_aspect.py b/tests/appsec/iast/aspects/test_str_aspect.py index 5666fb97baf..ad34bb730c0 100644 --- a/tests/appsec/iast/aspects/test_str_aspect.py +++ b/tests/appsec/iast/aspects/test_str_aspect.py @@ -527,13 +527,13 @@ def test_aspect_ljust_str_tainted(self): ljusted = mod.do_ljust(string_input, 4) # pylint: disable=no-member assert as_formatted_evidence(ljusted) == ":+-foo-+: " - def test_aspect_ljust_error_and_no_log_metric(self, telemetry_writer): + def test_aspect_ljust_error_with_tainted_gives_one_log_metric(self, telemetry_writer): string_input = create_taint_range_with_format(":+-foo-+:") with pytest.raises(TypeError): mod.do_ljust(string_input, "aaaaa") list_metrics_logs = list(telemetry_writer._logs) - assert len(list_metrics_logs) == 0 + assert len(list_metrics_logs) == 1 def test_zfill(self): # Not tainted @@ -572,4 +572,4 @@ def test_format(self): result = mod.do_format_fill(string_input) # pylint: disable=no-member # TODO format with params doesn't work correctly the assert should be # assert as_formatted_evidence(result) == ":+-foo -+:" - assert as_formatted_evidence(result) == "foo " + assert as_formatted_evidence(result) == ":+-foo-+:" From 3de0cf5f3777cc4cf7277bd9e19cbb03fcc9d26d Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Thu, 23 May 2024 12:25:42 +0200 Subject: [PATCH 103/104] chore(iast): smoke tests importing the top 100 popular modules (#9348) Smoke tests importing the top 100 popular modules. I skipped some modules, the reasons are: - no documentation - no related to web services (installation packages like setup tools, colorama is a terminal script...) - import error (`importlib.import_module(boto3)` raises an error) Here is the script that I used to extract the top popular modules from `https://pypistats.org/top` and `https://hugovk.github.io/top-pypi-packages/` https://gist.github.com/avara1986/87658015fd29cb7b38fd79210a04a091 ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. - [x] If change touches code that signs or publishes builds or packages, or handles credentials of any kind, I've requested a review from `@DataDog/security-design-and-guidance`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- tests/appsec/iast/aspects/conftest.py | 4 +- tests/appsec/iast_packages/test_packages.py | 190 +++++++++++++++++--- 2 files changed, 165 insertions(+), 29 deletions(-) diff --git a/tests/appsec/iast/aspects/conftest.py b/tests/appsec/iast/aspects/conftest.py index c732d79b01a..5f456db719b 100644 --- a/tests/appsec/iast/aspects/conftest.py +++ b/tests/appsec/iast/aspects/conftest.py @@ -4,8 +4,8 @@ from ddtrace.appsec._iast._ast.ast_patching import astpatch_module -def _iast_patched_module(module_name): - module = __import__(module_name, fromlist=[None]) +def _iast_patched_module(module_name, fromlist=[None]): + module = __import__(module_name, fromlist=fromlist) module_path, patched_source = astpatch_module(module) compiled_code = compile(patched_source, module_path, "exec") diff --git a/tests/appsec/iast_packages/test_packages.py b/tests/appsec/iast_packages/test_packages.py index 5d8f9c1ac98..73c169d9be8 100644 --- a/tests/appsec/iast_packages/test_packages.py +++ b/tests/appsec/iast_packages/test_packages.py @@ -1,3 +1,4 @@ +import importlib import json import os import subprocess @@ -5,22 +6,40 @@ import pytest +from ddtrace.constants import IAST_ENV from tests.appsec.appsec_utils import flask_server +from tests.appsec.iast.aspects.conftest import _iast_patched_module +from tests.utils import override_env class PackageForTesting: package_name = "" + import_name = "" package_version = "" url_to_test = "" expected_param = "test1234" expected_result1 = "" expected_result2 = "" extra_packages = [] - xfail = False + test_import = True + test_e2e = True - def __init__(self, name, version, expected_param, expected_result1, expected_result2, extras=[]): + def __init__( + self, + name, + version, + expected_param, + expected_result1, + expected_result2, + extras=[], + test_import=True, + test_e2e=True, + import_name=None, + ): self.package_name = name self.package_version = version + self.test_import = test_import + self.test_e2e = test_e2e if expected_param: self.expected_param = expected_param if expected_result1: @@ -29,6 +48,10 @@ def __init__(self, name, version, expected_param, expected_result1, expected_res self.expected_result2 = expected_result2 if extras: self.extra_packages = extras + if import_name: + self.import_name = import_name + else: + self.import_name = self.package_name @property def url(self): @@ -44,8 +67,10 @@ def _install(self, package_name, package_version): env.update(os.environ) # CAVEAT: we use subprocess instead of `pip.main(["install", package_fullversion])` due to pip package # doesn't work correctly with riot environment and python packages path - proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True, env=env) + proc = subprocess.Popen(cmd, stdout=sys.stdout, stderr=sys.stderr, close_fds=True, env=env) proc.wait() + print(proc.stdout) + print(proc.stderr) def install(self): self._install(self.package_name, self.package_version) @@ -53,8 +78,16 @@ def install(self): self._install(package_name, package_version) +# Top packages list imported from: +# https://pypistats.org/top +# https://hugovk.github.io/top-pypi-packages/ + +# pypular package is discarded because it is not a real top package +# wheel, importlib-metadata and pip is discarded because they are package to build projects PACKAGES = [ - PackageForTesting("charset-normalizer", "3.3.2", "my-bytes-string", "my-bytes-string", ""), + PackageForTesting( + "charset-normalizer", "3.3.2", "my-bytes-string", "my-bytes-string", "", import_name="charset_normalizer" + ), PackageForTesting( "google-api-python-client", "2.111.0", @@ -62,15 +95,17 @@ def install(self): "", "", extras=[("google-auth-oauthlib", "1.2.0"), ("google-auth-httplib2", "0.2.0")], + import_name="googleapiclient", ), PackageForTesting("idna", "3.6", "xn--eckwd4c7c.xn--zckzah", "ドメイン.テスト", "xn--eckwd4c7c.xn--zckzah"), - PackageForTesting("numpy", "1.24.4", "9 8 7 6 5 4 3", [3, 4, 5, 6, 7, 8, 9], 5), + # PackageForTesting("numpy", "1.24.4", "9 8 7 6 5 4 3", [3, 4, 5, 6, 7, 8, 9], 5), PackageForTesting( "python-dateutil", "2.8.2", "Sat Oct 11 17:13:46 UTC 2003", "Sat, 11 Oct 2003 17:13:46 GMT", "And the Easter of that year is: 2004-04-11", + import_name="dateutil", ), PackageForTesting( "PyYAML", @@ -78,6 +113,7 @@ def install(self): '{"a": 1, "b": {"c": 3, "d": 4}}', {"a": 1, "b": {"c": 3, "d": 4}}, "a: 1\nb:\n c: 3\n d: 4\n", + import_name="yaml", ), PackageForTesting("requests", "2.31.0", "", "", ""), PackageForTesting( @@ -87,36 +123,123 @@ def install(self): ["https", None, "www.datadoghq.com", None, "/", None, None], "www.datadoghq.com", ), - PackageForTesting("beautifulsoup4", "4.12.3", "", "", ""), + PackageForTesting("beautifulsoup4", "4.12.3", "", "", "", import_name="bs4"), + PackageForTesting("setuptools", "70.0.0", "", "", "", test_e2e=False), + PackageForTesting("six", "1.16.0", "", "", "", test_e2e=False), + PackageForTesting("s3transfer", "0.10.1", "", "", "", test_e2e=False), + PackageForTesting("certifi", "2024.2.2", "", "", "", test_e2e=False), + PackageForTesting("cryptography", "42.0.7", "", "", "", test_e2e=False), + PackageForTesting("fsspec", "2024.5.0", "", "", "", test_e2e=False, test_import=False), + PackageForTesting("boto3", "1.34.110", "", "", "", test_e2e=False, test_import=False), + # PackageForTesting("typing-extensions", "4.11.0", "", "", "", import_name="typing_extensions", test_e2e=False), + PackageForTesting("botocore", "1.34.110", "", "", "", test_e2e=False), + PackageForTesting("packaging", "24.0", "", "", "", test_e2e=False), + PackageForTesting("cffi", "1.16.0", "", "", "", test_e2e=False), + PackageForTesting( + "aiobotocore", "2.13.0", "", "", "", test_e2e=False, test_import=False, import_name="aiobotocore.session" + ), + PackageForTesting("s3fs", "2024.5.0", "", "", "", test_e2e=False, test_import=False), + PackageForTesting("google-api-core", "2.19.0", "", "", "", test_e2e=False, import_name="google"), + PackageForTesting("cffi", "1.16.0", "", "", "", test_e2e=False), + PackageForTesting("pycparser", "2.22", "", "", "", test_e2e=False), + # PackageForTesting("grpcio-status", "1.64.0", "", "", "", test_e2e=False), + # PackageForTesting("pandas", "2.2.2", "", "", "", test_e2e=False), + PackageForTesting("zipp", "3.18.2", "", "", "", test_e2e=False), + PackageForTesting("attrs", "23.2.0", "", "", "", test_e2e=False), + PackageForTesting("pyasn1", "0.6.0", "", "", "", test_e2e=False), + PackageForTesting("rsa", "4.9", "", "", "", test_e2e=False), + # PackageForTesting("protobuf", "5.26.1", "", "", "", test_e2e=False), + PackageForTesting("jmespath", "1.0.1", "", "", "", test_e2e=False), + PackageForTesting("click", "8.1.7", "", "", "", test_e2e=False), + PackageForTesting("pydantic", "2.7.1", "", "", "", test_e2e=False), + PackageForTesting("pytz", "2024.1", "", "", "", test_e2e=False), + # PackageForTesting("colorama", "0.4.6", "", "", "", test_e2e=False), + # PackageForTesting("awscli", "1.32.110", "", "", "", test_e2e=False), + PackageForTesting("markupsafe", "2.1.5", "", "", "", test_e2e=False), + PackageForTesting("jinja2", "3.1.4", "", "", "", test_e2e=False), + PackageForTesting("platformdirs", "4.2.2", "", "", "", test_e2e=False), + # PackageForTesting("pyjwt", "2.8.0", "", "", "", test_e2e=False, import_name="jwt"), + PackageForTesting("tomli", "2.0.1", "", "", "", test_e2e=False), + # PackageForTesting("googleapis-common-protos", "1.63.0", "", "", "", test_e2e=False), + PackageForTesting("filelock", "3.14.0", "", "", "", test_e2e=False), + # PackageForTesting("google-auth", "2.29.0", "", "", "", test_e2e=False), + PackageForTesting("wrapt", "1.16.0", "", "", "", test_e2e=False), + PackageForTesting("cachetools", "5.3.3", "", "", "", test_e2e=False), + PackageForTesting("pluggy", "1.5.0", "", "", "", test_e2e=False), + PackageForTesting("virtualenv", "20.26.2", "", "", "", test_e2e=False), + # PackageForTesting("docutils", "0.21.2", "", "", "", test_e2e=False), + # PackageForTesting("pyarrow", "16.1.0", "", "", "", test_e2e=False), + PackageForTesting("exceptiongroup", "1.2.1", "", "", "", test_e2e=False), + # PackageForTesting("jsonschema", "4.22.0", "", "", "", test_e2e=False), + PackageForTesting("requests-oauthlib", "2.0.0", "", "", "", test_e2e=False, import_name="requests_oauthlib"), + PackageForTesting("pyparsing", "3.1.2", "", "", "", test_e2e=False), + PackageForTesting("pytest", "8.2.1", "", "", "", test_e2e=False), + PackageForTesting("oauthlib", "3.2.2", "", "", "", test_e2e=False), + PackageForTesting("sqlalchemy", "2.0.30", "", "", "", test_e2e=False), + # PackageForTesting("pyasn1-modules", "0.4.0", "", "", "", test_e2e=False), + PackageForTesting("aiohttp", "3.9.5", "", "", "", test_e2e=False), + # PackageForTesting("scipy", "1.13.0", "", "", "", test_e2e=False, import_name="scipy.special"), + # PackageForTesting("isodate", "0.6.1", "", "", "", test_e2e=False), + PackageForTesting("multidict", "6.0.5", "", "", "", test_e2e=False), + PackageForTesting("iniconfig", "2.0.0", "", "", "", test_e2e=False), + PackageForTesting("psutil", "5.9.8", "", "", "", test_e2e=False), + PackageForTesting("soupsieve", "2.5", "", "", "", test_e2e=False), + PackageForTesting("yarl", "1.9.4", "", "", "", test_e2e=False), + # PackageForTesting("async-timeout", "4.0.3", "", "", "", test_e2e=False), + PackageForTesting("frozenlist", "1.4.1", "", "", "", test_e2e=False), + PackageForTesting("aiosignal", "1.3.1", "", "", "", test_e2e=False), + PackageForTesting("werkzeug", "3.0.3", "", "", "", test_e2e=False), + # PackageForTesting("pillow", "10.3.0", "", "", "", test_e2e=False, import_name="PIL.Image"), + PackageForTesting("tqdm", "4.66.4", "", "", "", test_e2e=False), + PackageForTesting("pygments", "2.18.0", "", "", "", test_e2e=False), + # PackageForTesting("grpcio", "1.64.0", "", "", "", test_e2e=False), + PackageForTesting("greenlet", "3.0.3", "", "", "", test_e2e=False), + PackageForTesting("pyopenssl", "24.1.0", "", "", "", test_e2e=False, import_name="OpenSSL.SSL"), + PackageForTesting("flask", "3.0.3", "", "", "", test_e2e=False), + PackageForTesting("decorator", "5.1.1", "", "", "", test_e2e=False), + PackageForTesting("pydantic-core", "2.18.2", "", "", "", test_e2e=False, import_name="pydantic_core"), + # PackageForTesting("lxml", "5.2.2", "", "", "", test_e2e=False, import_name="lxml.etree"), + PackageForTesting("requests-toolbelt", "1.0.0", "", "", "", test_e2e=False, import_name="requests_toolbelt"), + # PackageForTesting("openpyxl", "3.1.2", "", "", "", test_e2e=False, import_name="openpyxl.Workbook"), + PackageForTesting("tzdata", "2024.1", "", "", "", test_e2e=False), + # PackageForTesting("et-xmlfile", "1.1.0", "", "", "", test_e2e=False), + # PackageForTesting("importlib-resources", "6.4.0", "", "", "", test_e2e=False, import_name="importlib_resources"), + # PackageForTesting("proto-plus", "1.23.0", "", "", "", test_e2e=False), + # PackageForTesting("asn1crypto", "1.5.1", "", "", "", test_e2e=False), + PackageForTesting("coverage", "7.5.1", "", "", "", test_e2e=False), + # PackageForTesting("azure-core", "1.30.1", "", "", "", test_e2e=False, import_name="azure"), + PackageForTesting("distlib", "0.3.8", "", "", "", test_e2e=False), + PackageForTesting("tomlkit", "0.12.5", "", "", "", test_e2e=False), + # PackageForTesting("pynacl", "1.5.0", "", "", "", test_e2e=False), + PackageForTesting("itsdangerous", "2.2.0", "", "", "", test_e2e=False), + # PackageForTesting("annotated-types", "0.7.0", "", "", "", test_e2e=False), + PackageForTesting("sniffio", "1.3.1", "", "", "", test_e2e=False), + PackageForTesting("more-itertools", "10.2.0", "", "", "", test_e2e=False, import_name="more_itertools"), + # PackageForTesting("google-cloud-storage", "2.16.0", "", "", "", test_e2e=False), ] -def setup(): - for package in PACKAGES: - package.install() - - for package in PACKAGES: - with flask_server( - iast_enabled="false", tracer_enabled="true", remote_configuration_enabled="false", token=None - ) as context: - _, client, pid = context +@pytest.mark.parametrize("package", [package for package in PACKAGES if package.test_e2e]) +def test_packages_not_patched(package): + package.install() + with flask_server( + iast_enabled="false", tracer_enabled="true", remote_configuration_enabled="false", token=None + ) as context: + _, client, pid = context - response = client.get(package.url) + response = client.get(package.url) - assert response.status_code == 200 - content = json.loads(response.content) - assert content["param"] == package.expected_param - assert content["result1"] == package.expected_result1 - assert content["result2"] == package.expected_result2 - assert content["params_are_tainted"] is False + assert response.status_code == 200 + content = json.loads(response.content) + assert content["param"] == package.expected_param + assert content["result1"] == package.expected_result1 + assert content["result2"] == package.expected_result2 + assert content["params_are_tainted"] is False -@pytest.mark.skipif(sys.version_info >= (3, 12, 0), reason="Package not yet compatible with Python 3.12") -@pytest.mark.parametrize("package", PACKAGES) +@pytest.mark.parametrize("package", [package for package in PACKAGES if package.test_e2e]) def test_packages_patched(package): - if package.xfail: - pytest.xfail("Initial test failed for package: {}".format(package)) - + package.install() with flask_server(iast_enabled="true", remote_configuration_enabled="false", token=None) as context: _, client, pid = context @@ -128,3 +251,16 @@ def test_packages_patched(package): assert content["result1"] == package.expected_result1 assert content["result2"] == package.expected_result2 assert content["params_are_tainted"] is True + + +@pytest.mark.parametrize("package", [package for package in PACKAGES if package.test_import]) +def test_packages_not_patched_import(package): + package.install() + importlib.import_module(package.import_name) + + +@pytest.mark.parametrize("package", [package for package in PACKAGES if package.test_import]) +def test_packages_patched_import(package): + with override_env({IAST_ENV: "true"}): + package.install() + assert _iast_patched_module(package.import_name, fromlist=[]) From 609a306f5014218e45a5302c7cfb39e1de0e7e3d Mon Sep 17 00:00:00 2001 From: Federico Mon Date: Fri, 24 May 2024 08:24:21 +0200 Subject: [PATCH 104/104] ci: upgrade pytest for requests framework test (#9362) Avoids these kind of errors in the CI: https://github.com/DataDog/dd-trace-py/actions/runs/9204318180/job/25317642591?pr=9344#step:8:62 ## Checklist - [x] Change(s) are motivated and described in the PR description - [x] Testing strategy is described if automated tests are not included in the PR - [x] Risks are described (performance impact, potential for breakage, maintainability) - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed or label `changelog/no-changelog` is set - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)) - [x] Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] If this PR changes the public interface, I've notified `@DataDog/apm-tees`. ## Reviewer Checklist - [x] Title is accurate - [x] All changes are related to the pull request's stated goal - [x] Description motivates each change - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - [x] Testing strategy adequately addresses listed risks - [x] Change is maintainable (easy to change, telemetry, documentation) - [x] Release note makes sense to a user of the library - [x] Author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Co-authored-by: Emmett Butler <723615+emmettbutler@users.noreply.github.com> --- .github/workflows/test_frameworks.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/test_frameworks.yml b/.github/workflows/test_frameworks.yml index 0ad718b2520..e43c8a09515 100644 --- a/.github/workflows/test_frameworks.yml +++ b/.github/workflows/test_frameworks.yml @@ -675,6 +675,9 @@ jobs: - name: MarkupSafe fix if: needs.needs-run.outputs.outcome == 'success' run: pip install --upgrade MarkupSafe==2.0.1 + - name: Pytest fix + if: needs.needs-run.outputs.outcome == 'success' + run: pip install --upgrade pytest==5.4.3 - name: Run tests if: needs.needs-run.outputs.outcome == 'success' run: PYTHONPATH=../ddtrace/tests/debugging/exploration/ ddtrace-run pytest -p no:warnings tests