├── .github └── workflows │ ├── docs.yml │ ├── end2end-tests.yml │ ├── execution-invariance-tests.yml │ ├── python-publish.yml │ ├── test-cli-end2end.yml │ └── tests.yml ├── .gitignore ├── Dockerfile ├── INSTALL.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── STATUS.md ├── analyze.sh ├── benchmark.sh ├── benchmark.txt ├── corr.py ├── docs ├── benchmark.md ├── hierarchy2md.py ├── python.md └── release.md ├── end2end_tests ├── README.md ├── projects │ └── simple-with-pytest │ │ ├── LICENSE.txt │ │ ├── README.md │ │ ├── expected_coverage.json │ │ ├── expected_output.json │ │ ├── pyproject.toml │ │ ├── src │ │ └── simple_with_pytest │ │ │ ├── __about__.py │ │ │ ├── __init__.py │ │ │ ├── analysis.py │ │ │ └── simple.py │ │ └── tests │ │ ├── __init__.py │ │ └── test_simple.py └── scripts │ ├── check.py │ ├── revert.py │ ├── run_cli.sh │ ├── run_project.py │ └── run_single_project.sh ├── example_programs ├── __init__.py ├── no_auto_test_instrumentation.py ├── simpleIfs.py └── targetPrograms │ ├── __init__.py │ ├── branchSimple.py │ ├── callGraph_simple.py │ ├── demo.py │ ├── keyInList.py │ ├── keyInSet.py │ ├── mlMemory.py │ ├── mlMemory_no_issue.py │ ├── modifyListWhileIterating.py │ ├── simpleProgram.py │ ├── simpleProgram.py.orig │ ├── tempTestMichaelToRemove.py.orig │ ├── unnecessaryDoubleDictQuery.py │ └── unnecessaryDoubleDictQuery.py.orig ├── execution_invariance_test ├── __init__.py ├── analysis │ ├── LICENSE.txt │ ├── README.md │ ├── pyproject.toml │ ├── src │ │ └── analysis │ │ │ ├── __about__.py │ │ │ ├── __init__.py │ │ │ └── analysis.py │ └── tests │ │ └── __init__.py ├── projects │ └── simple-test │ │ ├── LICENSE.txt │ │ ├── README.md │ │ ├── pyproject.toml │ │ ├── src │ │ └── simple_test │ │ │ ├── __about__.py │ │ │ ├── __init__.py │ │ │ └── simple.py │ │ └── tests │ │ ├── stack_test.py │ │ └── test_simple.py ├── run_test.sh ├── test.py └── test_folders.txt ├── experiment.sh ├── extract_bad_tests.sh ├── filter_tests.py ├── prepare.sh ├── pyproject.toml ├── requirements.txt ├── run_all.sh ├── setup.py ├── src ├── dynapyt │ ├── __init__.py │ ├── analyses │ │ ├── BaseAnalysis.py │ │ ├── BranchCoverage.py │ │ ├── CallGraph.py │ │ ├── Demo.py │ │ ├── EventAnalysis.py │ │ ├── KeyInListAnalysis.py │ │ ├── LiteralAnalysis.py │ │ ├── MLMemoryAnalysis.py │ │ ├── ManipulateExec.py │ │ ├── MemoryAccessAnalysis.py │ │ ├── OnlyAddAnalysis.py │ │ ├── OperationAnalysis.py │ │ ├── SimpleTaintAnalysis.py │ │ ├── SimpleTestAnalysis.py │ │ ├── TraceAll.py │ │ ├── UnnecessaryDoubleDictQuery.py │ │ └── __init__.py │ ├── instrument │ │ ├── CodeInstrumenter.py │ │ ├── IIDs.py │ │ ├── __init__.py │ │ ├── filters.py │ │ └── instrument.py │ ├── post_run.py │ ├── run_all.py │ ├── run_analysis.py │ ├── run_instrumentation.py │ ├── runtime.py │ └── utils │ │ ├── AnalysisUtils.py │ │ ├── __init__.py │ │ ├── hierarchy.json │ │ ├── hooks.py │ │ ├── nodeLocator.py │ │ └── runtimeUtils.py └── nativetracer │ ├── __init__.py │ ├── instrument_tracer.py │ └── trc.py ├── tests ├── README.md ├── cleanup.py ├── conftest.py ├── filters │ ├── bool_literal_ignore │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── call │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── int_literal_ignore │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── int_literal_only │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── read_ignore │ │ ├── analysis.py │ │ ├── expected.txt │ │ ├── program.py │ │ └── skip.txt │ ├── read_only │ │ ├── analysis.py │ │ ├── expected.txt │ │ ├── program.py │ │ └── skip.txt │ ├── repr │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── string_literal_only │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ └── super │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py ├── manipulate_single_hook │ ├── decorator │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── enter_control_flow │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── literal │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── raise │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── read_subscript │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ └── write │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py ├── regression │ ├── add_assign_list_str │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── assert_no_space │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── async_for │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── async_return │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── attr_recursion │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── deep_site_sensitive_calls │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── delete_track_read │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── file_entry │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── filters_getattr │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── function_exit │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── gen_expr_pos_arg │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── inline_break_continue │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── lhs_read_sub │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── list_comp_if │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── no_analysis_exception │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── yield_from │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ └── zip │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py ├── run_single_test.py ├── test_analysis │ ├── call_graph │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ └── multiple_analyses │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py ├── test_coverage │ ├── multi_file │ │ ├── analysis.py │ │ ├── exp_coverage.json │ │ ├── expected.txt │ │ ├── program.py │ │ └── program2.py │ ├── runtime_event │ │ ├── analysis.py │ │ ├── exp_coverage.json │ │ ├── expected.txt │ │ └── program.py │ └── single_hook │ │ ├── analysis.py │ │ ├── exp_coverage.json │ │ ├── expected.txt │ │ └── program.py └── trace_single_hook │ ├── add_assign │ ├── analysis.py │ ├── expected.txt │ └── program.py │ ├── annotated_assignment │ ├── analysis.py │ ├── expected.txt │ └── program.py │ ├── call │ ├── analysis.py │ ├── expected.txt │ └── program.py │ ├── call_builtin │ ├── analysis.py │ ├── expected.txt │ └── program.py │ ├── class_attr │ ├── analysis.py │ ├── expected.txt │ └── program.py │ ├── comparison │ ├── analysis.py │ ├── expected.txt │ └── program.py │ ├── decorators │ ├── builtin_decorator │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── decorator_class_attribute │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── decorator_func_with_args │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── decorator_runtime_controlflow │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── decorator_simple │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── decorator_with_args │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ └── nested_decorator │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── elif │ ├── analysis.py │ ├── expected.txt │ └── program.py │ ├── for_comprehension │ ├── analysis.py │ ├── expected.txt │ └── program.py │ ├── if │ ├── analysis.py │ ├── expected.txt │ └── program.py │ ├── if_expression │ ├── analysis.py │ ├── expected.txt │ └── program.py │ ├── if_expression_side_effects │ ├── analysis.py │ ├── expected.txt │ └── program.py │ ├── in │ ├── analysis.py │ ├── expected.txt │ └── program.py │ ├── lhs_attr │ ├── analysis.py │ ├── expected.txt │ └── program.py │ ├── list │ ├── analysis.py │ ├── expected.txt │ └── program.py │ ├── literals │ ├── analysis.py │ ├── expected.txt │ └── program.py │ ├── multiply │ ├── analysis.py │ ├── expected.txt │ └── program.py │ ├── simple_delete │ ├── analysis.py │ ├── expected.txt │ └── program.py │ ├── simple_read │ ├── analysis.py │ ├── expected.txt │ └── program.py │ ├── string_literal │ ├── analysis.py │ ├── expected.txt │ └── program.py │ ├── try │ ├── analysis.py │ ├── expected.txt │ └── program.py │ ├── try_clean_exit │ ├── analysis.py │ ├── expected.txt │ └── program.py │ ├── try_control_flow_event │ ├── analysis.py │ ├── expected.txt │ └── program.py │ ├── version_write │ ├── __init__.py │ ├── analysis.py │ ├── expected.txt │ └── program.py │ ├── while │ ├── analysis.py │ ├── expected.txt │ └── program.py │ ├── with │ ├── with │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── with_multiple_items │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── with_multiple_items_as_specifier_case_1 │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── with_multiple_items_as_specifier_case_2 │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── with_multiple_items_suppress_exception │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── with_multiple_raise_exception │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── with_nested_call │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── with_raise_exception │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── with_runtime_control_flow_hooks │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── with_suppress_exception │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ ├── with_with_attribute_call │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ └── with_without_as_specifier │ │ ├── analysis.py │ │ ├── expected.txt │ │ └── program.py │ └── write │ ├── analysis.py │ ├── expected.txt │ └── program.py └── tutorial ├── Dockerfile ├── README.md ├── solution-analyses ├── BranchCoverageAnalysis.py ├── CallGraphAnalysis.py ├── OppositeBranchAnalysis.py └── SlowStringConcatAnalysis.py ├── task1 └── main.py ├── task2 └── main.py ├── task3 ├── a.py └── main.py ├── task4 └── main.py ├── task5 └── main.py └── tutorial-analyses ├── LICENSE.txt ├── README.md ├── pyproject.toml ├── src └── tutorial_analyses │ ├── BranchCoverageAnalysis.py │ ├── CallGraphAnalysis.py │ ├── OppositeBranchAnalysis.py │ ├── SlowStringConcatAnalysis.py │ ├── __about__.py │ └── __init__.py └── tests └── __init__.py /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: docs 2 | 3 | # build the documentation whenever there are new commits on main 4 | on: 5 | push: 6 | branches: 7 | - main 8 | # Alternative: only build for tags. 9 | # tags: 10 | # - '*' 11 | 12 | # security: restrict permissions for CI jobs. 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | # Build the documentation and upload the static HTML files as an artifact. 18 | build: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v4 22 | - uses: actions/setup-python@v5 23 | with: 24 | python-version: '3.13' 25 | 26 | - run: pip install -r requirements.txt 27 | - run: pip install pdoc 28 | - run: pip install -e . 29 | # ADJUST THIS: build your documentation into docs/. 30 | # We use a custom build script for pdoc itself, ideally you just run `pdoc -o docs/ ...` here. 31 | - run: python docs/hierarchy2md.py 32 | - run: pdoc src/dynapyt/analyses/TraceAll.py -o docs/ 33 | 34 | - uses: actions/upload-pages-artifact@v3 35 | with: 36 | path: docs/ 37 | 38 | # Deploy the artifact to GitHub pages. 39 | # This is a separate job so that only actions/deploy-pages has the necessary permissions. 40 | deploy: 41 | needs: build 42 | runs-on: ubuntu-latest 43 | permissions: 44 | pages: write 45 | id-token: write 46 | environment: 47 | name: github-pages 48 | url: ${{ steps.deployment.outputs.page_url }} 49 | steps: 50 | - id: deployment 51 | uses: actions/deploy-pages@v4 -------------------------------------------------------------------------------- /.github/workflows/end2end-tests.yml: -------------------------------------------------------------------------------- 1 | name: end2end tests 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | 8 | strategy: 9 | matrix: 10 | python-version: ["3.10", "3.11", "3.12", "3.13"] 11 | os: ["ubuntu-latest"] 12 | 13 | runs-on: ${{ matrix.os }} 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Set up Python ${{ matrix.python-version }} 18 | uses: actions/setup-python@v5 19 | with: 20 | python-version: ${{ matrix.python-version }} 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install hatch 25 | pip install pytest 26 | - name: Install DynaPyt 27 | run: | 28 | pip install -e . 29 | - name: Run the test script 30 | run: | 31 | hatch run end2end:run -------------------------------------------------------------------------------- /.github/workflows/execution-invariance-tests.yml: -------------------------------------------------------------------------------- 1 | name: execution invariance tests 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | 8 | strategy: 9 | matrix: 10 | python-version: ["3.10", "3.11", "3.12", "3.13"] 11 | os: ["ubuntu-latest"] 12 | 13 | runs-on: ${{ matrix.os }} 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Set up Python ${{ matrix.python-version }} 18 | uses: actions/setup-python@v5 19 | with: 20 | python-version: ${{ matrix.python-version }} 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install hatch 25 | pip install pytest-json-report 26 | - name: Install DynaPyt 27 | run: | 28 | pip install -e . 29 | - name: Run the test script 30 | run: | 31 | hatch run exec_invariance_test:run -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will upload a Python Package using Twine when a release is created 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: Upload Python Package 10 | 11 | on: 12 | release: 13 | types: [published] 14 | workflow_dispatch: 15 | 16 | permissions: 17 | contents: read 18 | 19 | jobs: 20 | deploy: 21 | 22 | runs-on: ubuntu-latest 23 | 24 | steps: 25 | - uses: actions/checkout@v4 26 | - name: Set up Python 27 | uses: actions/setup-python@v5 28 | with: 29 | python-version: '3.x' 30 | - name: Install dependencies 31 | run: | 32 | python -m pip install --upgrade pip 33 | pip install build 34 | - name: Build package 35 | run: python -m build 36 | - name: Publish package 37 | uses: pypa/gh-action-pypi-publish@release/v1 38 | with: 39 | user: __token__ 40 | password: ${{ secrets.PYPI_API_KEY }} 41 | -------------------------------------------------------------------------------- /.github/workflows/test-cli-end2end.yml: -------------------------------------------------------------------------------- 1 | name: Tests CLI end2end 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | 8 | strategy: 9 | matrix: 10 | python-version: ["3.10", "3.11", "3.12", "3.13"] 11 | os: ["ubuntu-latest"] 12 | 13 | runs-on: ${{ matrix.os }} 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Set up Python ${{ matrix.python-version }} 18 | uses: actions/setup-python@v5 19 | with: 20 | python-version: ${{ matrix.python-version }} 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install hatch 25 | pip install pytest 26 | - name: Install DynaPyt 27 | run: | 28 | pip install -e . 29 | - name: Run the test script 30 | run: | 31 | hatch run end2end:cli -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | 8 | strategy: 9 | matrix: 10 | python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] 11 | os: ["ubuntu-latest", "windows-latest", "macos-latest"] 12 | 13 | runs-on: ${{ matrix.os }} 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Set up Python ${{ matrix.python-version }} 18 | uses: actions/setup-python@v5 19 | with: 20 | python-version: ${{ matrix.python-version }} 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install pytest 25 | - name: Install DynaPyt 26 | run: | 27 | pip install . 28 | - name: Test with pytest 29 | run: | 30 | pytest tests -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11-slim 2 | 3 | ARG repo 4 | 5 | WORKDIR /DynaPyt 6 | 7 | RUN pip install --upgrade pip setuptools wheel 8 | 9 | COPY . . 10 | 11 | RUN pip install . 12 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | ## Install using pip 2 | ``` 3 | pip install dynapyt 4 | ``` 5 | 6 | ## Install from source 7 | ``` 8 | git clone https://github.com/sola-st/DynaPyt.git 9 | cd DynaPyt 10 | pip install -r requirements.txt 11 | pip install . 12 | ``` -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 Aryaz Eghbali, Michael Pradel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include src/dynapyt/utils/hierarchy.json -------------------------------------------------------------------------------- /STATUS.md: -------------------------------------------------------------------------------- 1 | Evaluated - Functional 2 | 3 | We are applying to obtain the "Evaluated - Functional" badge for DynaPyt. 4 | The installation procedure, instrumenting code, and running the analysis are all documented. 5 | Moreover, several analyses are implemented and included in this repository as guiding examples for analysis developers. 6 | Analysis hooks are also well-documented both inside the code, and in the accompanying document. 7 | We have also provided a simple script to run end-to-end experiments from the paper to reduce the hassle of reproducing the results. -------------------------------------------------------------------------------- /analyze.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Usage: bash analyze.sh --source --analysis 3 | # e.g.: bash analyze.sh --source test/targetPrograms/demo.py --analysis TraceAll 4 | 5 | while [[ "$#" -gt 0 ]]; do 6 | case $1 in 7 | -s|--source) source="$2"; shift ;; 8 | -a|--analysis) analysis="$2"; shift ;; 9 | *) echo "Unknown parameter passed: $1"; exit 1 ;; 10 | esac 11 | shift 12 | done 13 | 14 | echo $source 15 | echo $analysis -------------------------------------------------------------------------------- /benchmark.sh: -------------------------------------------------------------------------------- 1 | curr=$(pwd) 2 | while read repo test; do 3 | cd $curr 4 | bash ./experiment.sh $repo $test TraceAll 5 | cd $curr 6 | bash ./experiment.sh $repo $test original 7 | done < benchmark.txt -------------------------------------------------------------------------------- /benchmark.txt: -------------------------------------------------------------------------------- 1 | ansible-devel test 2 | django-main tests 3 | keras-master keras/tests 4 | pandas-main pandas/tests 5 | requests-main tests 6 | rich-master tests 7 | scikit-learn-main sklearn/tests 8 | scrapy-master tests 9 | thefuck-master tests -------------------------------------------------------------------------------- /corr.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | a = [] 3 | a.append((6*60 + 59, 2188, 176173)) 4 | a.append((14*60 + 7, 3603, 318602)) 5 | a.append((5*60 + 41, 678, 155407)) 6 | a.append((12*60 + 32, 2727, 358195)) 7 | a.append((16, 54, 6370)) 8 | a.append((57, 178, 24362)) 9 | a.append((6*60 + 52, 1419, 180185)) 10 | a.append((60 + 49, 505, 37181)) 11 | a.append((60 + 21, 620, 12070)) 12 | 13 | d = [x[0] for x in a] 14 | f = [x[1] for x in a] 15 | l = [x[2] for x in a] 16 | print(d) 17 | print(f) 18 | print(l) 19 | print(np.corrcoef(d, f)) 20 | print(np.corrcoef(d, l)) -------------------------------------------------------------------------------- /docs/benchmark.md: -------------------------------------------------------------------------------- 1 | For all projects first: 2 | ``` 3 | pip install pytest pytest-xdist 4 | ``` 5 | Pandas: 6 | ``` 7 | pip install numpy pytz Cython hypothesis 8 | python setup.py install 9 | bash test_fast.sh 10 | ``` 11 | Django: 12 | ``` 13 | pip install . 14 | pip install -r tests/requirements./py3.txt 15 | pytest 16 | ``` 17 | Keras: 18 | ``` 19 | pip install -r requirements.txt 20 | cd keras 21 | python runall_tests.py 22 | ``` 23 | Requests: 24 | ``` 25 | pytest 26 | ``` 27 | Rich: 28 | ``` 29 | pip install pygments commonmark 30 | pytest 31 | ``` 32 | Scikit-learn: 33 | ``` 34 | pip install -r requirements.txt 35 | python setup.py install 36 | pytest --showlocals -v sklearn --durations=20 37 | ``` 38 | Scrapy: 39 | ``` 40 | pip install twisted lxml parsel w3lib cryptography itemadapter pydispatch pyOpenSSL protego 41 | pip install -r tests/requirements.txt 42 | pytest 43 | ``` 44 | Thefuck: 45 | ``` 46 | pip install -r requirements.txt 47 | python setup.py install 48 | pytest 49 | ``` 50 | -------------------------------------------------------------------------------- /docs/hierarchy2md.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | import importlib.util 4 | import sys 5 | 6 | module_name = "dynapyt.analyses.TraceAll" 7 | spec = importlib.util.spec_from_file_location( 8 | module_name, 9 | Path(__file__).parent.resolve() 10 | / ".." 11 | / "src" 12 | / "dynapyt" 13 | / "analyses" 14 | / "TraceAll.py", 15 | ) 16 | module = importlib.util.module_from_spec(spec) 17 | sys.modules[module_name] = module 18 | spec.loader.exec_module(module) 19 | 20 | hooks = dir(module.TraceAll()) 21 | hooks = [h for h in hooks if not h.startswith("__")] 22 | hooks.remove("iid_to_location") 23 | hooks.remove("asts") 24 | hooks.remove("_get_ast") 25 | hooks.remove("location_to_iid") 26 | hooks.remove("log") 27 | hooks.remove("conf") 28 | hooks.remove("output_dir") 29 | traceall_hooks = set(hooks) 30 | 31 | with open( 32 | Path(__file__).parent.resolve() 33 | / ".." 34 | / "src" 35 | / "dynapyt" 36 | / "utils" 37 | / "hierarchy.json", 38 | "r", 39 | ) as f: 40 | hierarchy = json.load(f) 41 | 42 | hierarchy_hooks = set() 43 | q = [hierarchy] 44 | while len(q) > 0: 45 | curr = q.pop() 46 | for k, v in curr.items(): 47 | if len(v.items()) > 0: 48 | q.append(v) 49 | hierarchy_hooks.add(k) 50 | 51 | non_matching = traceall_hooks.symmetric_difference(hierarchy_hooks) 52 | if len(non_matching) > 0: 53 | raise Exception("Hooks do not match hierarchy", non_matching) 54 | 55 | 56 | def to_string_list(root): 57 | if len(root.items()) == 0: 58 | return [] 59 | res = [] 60 | its = len(root.items()) 61 | count = 0 62 | for k, v in root.items(): 63 | count += 1 64 | if len(v.items()) > 0: 65 | res.append(f"└ [{k}](#TraceAll.{k}) ") 66 | inner_res = to_string_list(v) 67 | for i in range(len(inner_res)): 68 | inner_res[i] = ( 69 | ("│ " + "  " * 3) if count < its else "  " * 5 70 | ) + inner_res[i] 71 | res.extend(inner_res) 72 | else: 73 | res.append(f"└ [{k}](#TraceAll.{k}) ") 74 | return res 75 | 76 | 77 | with open(Path(__file__).parent.resolve() / "hooks.md", "w") as f: 78 | # f.write("```\n" + "\n".join(to_string_list(hierarchy)) + "\n```\n") 79 | f.write("\n".join(to_string_list(hierarchy)) + "\n") 80 | -------------------------------------------------------------------------------- /docs/python.md: -------------------------------------------------------------------------------- 1 | Class Attributes and Functions 2 | ============================== 3 | Each function defined in a class does not have access to the identifiers inside the class. So 4 | ``` 5 | class X: 6 | a = 0 7 | def foo(): 8 | print(a) # Error 9 | 10 | def bar(): 11 | foo() # Error 12 | ``` 13 | 14 | However, when the class body gets executed the method declarations are executed and hence 15 | ``` 16 | class X: 17 | a = 0 18 | def foo(): 19 | print(a) # Error 20 | 21 | def bar(foo=foo): 22 | foo() # Works 23 | ``` 24 | . 25 | -------------------------------------------------------------------------------- /docs/release.md: -------------------------------------------------------------------------------- 1 | How to publish the latest version on this repository to PyPI: 2 | 0. Make sure that the main branch is passing the tests and is ready for publishing. 3 | 1. Update the version number both in `setup.py` and `src/dynapyt/__init__.py`. 4 | 2. Create a release on this repository. The appropriate GitHub action will publish the current version to PyPI. 5 | -------------------------------------------------------------------------------- /end2end_tests/README.md: -------------------------------------------------------------------------------- 1 | # End-to-end Tests 2 | This directory holds the projects and scripts used for testing DynaPyt end-to-end. 3 | The `projects` directory contains the projects used for testing. 4 | Each project should also have an `analysis` module from its root. For example, a project named `foo` should have `foo.analysis`, which is the analysis class used for the tests. 5 | The `scripts` directory contains the scripts required to run the tests. 6 | 7 | To run the end-to-end tests: 8 | - Install `hatch` with `pip install hatch`. 9 | - Run `hatch run end2end:run`. 10 | - Since the execution does not use pytest, the success of the tests depend on the exit code of the previous step. -------------------------------------------------------------------------------- /end2end_tests/projects/simple-with-pytest/LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024-present Aryaz Eghbali 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /end2end_tests/projects/simple-with-pytest/README.md: -------------------------------------------------------------------------------- 1 | # Simple with Pytest 2 | 3 | [![PyPI - Version](https://img.shields.io/pypi/v/simple-with-pytest.svg)](https://pypi.org/project/simple-with-pytest) 4 | [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/simple-with-pytest.svg)](https://pypi.org/project/simple-with-pytest) 5 | 6 | ----- 7 | 8 | **Table of Contents** 9 | 10 | - [Installation](#installation) 11 | - [License](#license) 12 | 13 | ## Installation 14 | 15 | ```console 16 | pip install simple-with-pytest 17 | ``` 18 | 19 | ## License 20 | 21 | `simple-with-pytest` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license. 22 | -------------------------------------------------------------------------------- /end2end_tests/projects/simple-with-pytest/expected_coverage.json: -------------------------------------------------------------------------------- 1 | { 2 | "simple-with-pytest/src/simple_with_pytest/simple.py.orig": { 3 | "2": {"Analysis": 2}, 4 | "6": {"Analysis": 2}, 5 | "10": {"Analysis": 2} 6 | } 7 | } -------------------------------------------------------------------------------- /end2end_tests/projects/simple-with-pytest/expected_output.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "workers": 6 4 | }, 5 | "output": [ 6 | [ 7 | "begin execution", 8 | "1 Add 1 = 2", 9 | "end execution" 10 | ], 11 | [ 12 | "begin execution", 13 | "2 Add 1 = 3", 14 | "end execution" 15 | ], 16 | [ 17 | "begin execution", 18 | "1 Multiply 2 = 2", 19 | "end execution" 20 | ], 21 | [ 22 | "begin execution", 23 | "2 Multiply 2 = 4", 24 | "end execution" 25 | ], 26 | [ 27 | "begin execution", 28 | "1 Add 2 = 3", 29 | "end execution" 30 | ], 31 | [ 32 | "begin execution", 33 | "2 Add 3 = 5", 34 | "end execution" 35 | ] 36 | ] 37 | } -------------------------------------------------------------------------------- /end2end_tests/projects/simple-with-pytest/src/simple_with_pytest/__about__.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024-present Aryaz Eghbali 2 | # 3 | # SPDX-License-Identifier: MIT 4 | # DYNAPYT: DO NOT INSTRUMENT 5 | __version__ = "0.0.1" 6 | -------------------------------------------------------------------------------- /end2end_tests/projects/simple-with-pytest/src/simple_with_pytest/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024-present Aryaz Eghbali 2 | # 3 | # SPDX-License-Identifier: MIT 4 | # DYNAPYT: DO NOT INSTRUMENT 5 | -------------------------------------------------------------------------------- /end2end_tests/projects/simple-with-pytest/src/simple_with_pytest/analysis.py: -------------------------------------------------------------------------------- 1 | # DYNAPYT: DO NOT INSTRUMENT 2 | 3 | import json 4 | from uuid import uuid4 5 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 6 | 7 | 8 | class Analysis(BaseAnalysis): 9 | def __init__(self, output_dir: str = None): 10 | self.output_dir = output_dir 11 | self.output = [] 12 | 13 | def begin_execution(self): 14 | self.output.append("begin execution") 15 | 16 | def end_execution(self): 17 | self.output.append("end execution") 18 | with open(f"{self.output_dir}/output-{str(uuid4())}.json", "w") as f: 19 | json.dump(self.output, f, indent=2) 20 | 21 | def binary_operation(self, dyn_ast, iid, op, left, right, result): 22 | self.output.append(f"{left} {op} {right} = {result}") 23 | -------------------------------------------------------------------------------- /end2end_tests/projects/simple-with-pytest/src/simple_with_pytest/simple.py: -------------------------------------------------------------------------------- 1 | def add_one(x: int) -> int: 2 | return x + 1 3 | 4 | 5 | def multiply_two(x: int) -> int: 6 | return x * 2 7 | 8 | 9 | def add_together(x: int, y: int) -> int: 10 | return x + y 11 | -------------------------------------------------------------------------------- /end2end_tests/projects/simple-with-pytest/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024-present Aryaz Eghbali 2 | # 3 | # SPDX-License-Identifier: MIT 4 | # DYNAPYT: DO NOT INSTRUMENT 5 | -------------------------------------------------------------------------------- /end2end_tests/projects/simple-with-pytest/tests/test_simple.py: -------------------------------------------------------------------------------- 1 | from simple_with_pytest.simple import add_one, multiply_two, add_together 2 | 3 | 4 | def test_add_one_1(): 5 | assert add_one(1) == 2 6 | 7 | 8 | def test_add_one_2(): 9 | assert add_one(2) == 3 10 | 11 | 12 | def test_multiply_two_1(): 13 | assert multiply_two(1) == 2 14 | 15 | 16 | def test_multiply_two_2(): 17 | assert multiply_two(2) == 4 18 | 19 | 20 | def test_add_together_1_2(): 21 | assert add_together(1, 2) == 3 22 | 23 | 24 | def test_add_together_2_3(): 25 | assert add_together(2, 3) == 5 26 | -------------------------------------------------------------------------------- /end2end_tests/scripts/check.py: -------------------------------------------------------------------------------- 1 | import fire 2 | import json 3 | from pathlib import Path 4 | from dynapyt.utils.runtimeUtils import match_output, match_coverage 5 | 6 | 7 | def check(project_dir: str, output_dir: str, coverage_dir: str): 8 | with open(Path(output_dir) / "output.json", "r") as f: 9 | output = json.load(f) 10 | with open(Path(project_dir) / "expected_output.json", "r") as f: 11 | expected = json.load(f)["output"] 12 | if not match_output(output, expected): 13 | raise ValueError( 14 | f"Output does not match expected output: Expected:\n{expected}\nActual:\n{output}\n" 15 | ) 16 | 17 | with open(Path(coverage_dir) / "coverage.json", "r") as f: 18 | coverage = json.load(f) 19 | with open(Path(project_dir) / "expected_coverage.json", "r") as f: 20 | expected = json.load(f) 21 | if not match_coverage(coverage, expected): 22 | raise ValueError( 23 | f"Coverage does not match expected coverage: Expected:\n{expected}\nActual:\n{coverage}\n" 24 | ) 25 | 26 | 27 | if __name__ == "__main__": 28 | fire.Fire(check) 29 | -------------------------------------------------------------------------------- /end2end_tests/scripts/revert.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import fire 3 | 4 | 5 | def revert(root: str): 6 | root = Path(root) 7 | for f in root.glob("**/*-dynapyt.json"): 8 | if f.is_file(): 9 | f.unlink() 10 | for f in root.glob("**/*.py"): 11 | orig = f.with_suffix(".py.orig") 12 | if orig.is_file(): 13 | f.unlink() 14 | orig.rename(f) 15 | 16 | 17 | if __name__ == "__main__": 18 | fire.Fire(revert) 19 | -------------------------------------------------------------------------------- /end2end_tests/scripts/run_cli.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Usage: bash run_cli.sh 3 | # Example: bash run_cli.sh simple-with-pytest simple_with_pytest "pytest -n 6 tests" 4 | 5 | RED='\033[0;31m' 6 | GREEN='\033[0;32m' 7 | RESET='\033[0m' 8 | 9 | python -m venv venv 10 | source venv/bin/activate 11 | SCRIPTS_DIR=$(dirname $(realpath "$0")) 12 | echo "Installing..." 13 | pip install -e $SCRIPTS_DIR/../..[end2end] 14 | pip install -e $SCRIPTS_DIR/../projects/$1 15 | if [ -f $SCRIPTS_DIR/../projects/$1/requirements.txt ]; then 16 | pip install -r $SCRIPTS_DIR/../projects/$1/requirements.txt 17 | fi 18 | # python -c "import $2.analysis; print($2.analysis.Analysis)" 19 | echo "Instrumenting..." 20 | python -m dynapyt.run_instrumentation --directory $SCRIPTS_DIR/../projects/$1 --analysis="$2.analysis.Analysis" 21 | echo "Setting up the analyses..." 22 | uniqueID=$(python -c "import uuid; print(uuid.uuid4())") 23 | export DYNAPYT_SESSION_ID=$uniqueID 24 | temp_dir="${TMPDIR:-/tmp}" 25 | mkdir $temp_dir/dynapyt_output-$uniqueID 26 | echo "$2.analysis.Analysis;output_dir=$temp_dir/dynapyt_output-$uniqueID" > $temp_dir/dynapyt_analyses-$uniqueID.txt 27 | cat $temp_dir/dynapyt_analyses-$uniqueID.txt 28 | echo "Setting up coverage..." 29 | export DYNAPYT_COVERAGE="$temp_dir/dynapyt_coverage-$uniqueID" 30 | cd $SCRIPTS_DIR/../projects/$1 31 | echo "Running..." 32 | $3 33 | cd $SCRIPTS_DIR 34 | echo "Collecting the results..." 35 | python -m dynapyt.post_run --coverage_dir="$temp_dir/dynapyt_coverage-$uniqueID" --output_dir="$temp_dir/dynapyt_output-$uniqueID" 36 | echo "Checking correctness..." 37 | python ./check.py --project_dir="$SCRIPTS_DIR/../projects/$1" --output_dir="$temp_dir/dynapyt_output-$uniqueID" --coverage_dir="$temp_dir/dynapyt_coverage-$uniqueID" 38 | if [ $? -eq 0 ]; then 39 | echo -e "${GREEN}Passed.${RESET}" 40 | else 41 | echo -e "${RED}Failed.${RESET}" 42 | fi 43 | echo "Cleaning up..." 44 | python ./revert.py --root="$SCRIPTS_DIR/../projects/$1" 45 | rm $temp_dir/dynapyt_analyses-$uniqueID.txt 46 | rm -rf $temp_dir/dynapyt_coverage-$uniqueID 47 | rm -rf $temp_dir/dynapyt_output-$uniqueID 48 | deactivate 49 | rm -rf venv 50 | echo "Done." -------------------------------------------------------------------------------- /end2end_tests/scripts/run_single_project.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Usage: ./run_single_project.sh 3 | # Example: ./run_single_project.sh simple-with-pytest simple_with_pytest tests 4 | 5 | python -m venv venv 6 | source venv/bin/activate 7 | SCRIPTS_DIR=$(dirname $(realpath "$0")) 8 | pip install -e $SCRIPTS_DIR/../..[end2end] 9 | pip install -e $SCRIPTS_DIR/../projects/$1 10 | if [ -f $SCRIPTS_DIR/../projects/$1/requirements.txt ]; then 11 | pip install -r $SCRIPTS_DIR/../projects/$1/requirements.txt 12 | fi 13 | python $SCRIPTS_DIR/run_project.py --project_path="$1" --module_name="$2" --path_to_tests="$3" 14 | deactivate 15 | rm -rf venv 16 | -------------------------------------------------------------------------------- /example_programs/__init__.py: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------------------------------- /example_programs/no_auto_test_instrumentation.py: -------------------------------------------------------------------------------- 1 | from os import walk, path 2 | from dynapyt.instrument.instrument import instrument_code 3 | from dynapyt.instrument.IIDs import IIDs 4 | from shutil import move 5 | import pytest 6 | from dynapyt.utils.hooks import get_hooks_from_analysis 7 | 8 | selected_hooks = get_hooks_from_analysis("dynapyt.analyses.TraceAll", "TraceAll") 9 | 10 | 11 | @pytest.fixture(autouse=True) 12 | def reset(): 13 | for dirpath, dirnames, filenames in walk( 14 | path.normpath(path.join(path.dirname(path.abspath(__file__)), "../test_files")) 15 | ): 16 | for f in filenames: 17 | if f.endswith(".py.orig"): 18 | move(path.join(dirpath, f), path.join(dirpath, f[:-5])) 19 | 20 | 21 | def pytest_generate_tests(metafunc): 22 | test_f = [] 23 | correct_f = [] 24 | for dirpath, dirnames, filenames in walk( 25 | path.normpath(path.join(path.dirname(path.abspath(__file__)), "../test_files")) 26 | ): 27 | test_f.extend( 28 | path.join(dirpath, filename) 29 | for filename in filenames 30 | if filename.endswith(".py") 31 | ) 32 | correct_f.extend( 33 | path.join(dirpath, filename + ".correct") 34 | for filename in filenames 35 | if filename.endswith(".py") 36 | ) 37 | metafunc.parametrize( 38 | "test_file,correct_file", 39 | list(zip(test_f, correct_f)), 40 | ids=[tf.split("/")[-1][:-3] for tf in test_f], 41 | ) 42 | 43 | 44 | def test_instrumentation(test_file, correct_file): 45 | with open(test_file) as tf, open(correct_file) as cf: 46 | test_code = tf.read() 47 | correct_code = cf.read() 48 | iids = IIDs(test_file) 49 | instrumented_code = instrument_code(test_code, test_file, iids, selected_hooks) 50 | if instrumented_code is None: 51 | instrumented_code = test_code 52 | l_ins_code = instrumented_code.split("\n") 53 | l_cor_code = correct_code.split("\n") 54 | for l1, l2 in zip(l_ins_code, l_cor_code): 55 | assert l1 == l2 56 | # assert instrumented_code == correct_code 57 | -------------------------------------------------------------------------------- /example_programs/simpleIfs.py: -------------------------------------------------------------------------------- 1 | a = 23 2 | if a > 5: 3 | print(f"{a} > 5") 4 | else: 5 | print(f"{a} <= 5") -------------------------------------------------------------------------------- /example_programs/targetPrograms/__init__.py: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------------------------------- /example_programs/targetPrograms/branchSimple.py: -------------------------------------------------------------------------------- 1 | # DYNAPYT: DO NOT INSTRUMENT 2 | 3 | 4 | from dynapyt.runtime import _catch_ 5 | from dynapyt.runtime import _enter_ctrl_flow_, _exit_ctrl_flow_ 6 | 7 | _dynapyt_ast_ = __file__ + ".orig" 8 | try: 9 | a = 0 10 | while _enter_ctrl_flow_(_dynapyt_ast_, 2, a < 10): 11 | if _enter_ctrl_flow_(_dynapyt_ast_, 1, a % 2 == 0): 12 | print('even') 13 | _exit_ctrl_flow_(_dynapyt_ast_, 1) 14 | a += 1 15 | else: 16 | _exit_ctrl_flow_(_dynapyt_ast_, 2) 17 | except Exception as _dynapyt_exception_: 18 | _catch_(_dynapyt_exception_) 19 | -------------------------------------------------------------------------------- /example_programs/targetPrograms/callGraph_simple.py: -------------------------------------------------------------------------------- 1 | # DYNAPYT: DO NOT INSTRUMENT 2 | 3 | 4 | from dynapyt.runtime import _dynapyt_parse_to_ast_ 5 | from dynapyt.runtime import _catch_ 6 | from dynapyt.runtime import _call_ 7 | 8 | _dynapyt_source_code_ = "def foo():\n print('foo')\n bar()\n\ndef bar():\n print('bar')\n\ndef spam():\n print('spam')\n foo()\n\nspam()" 9 | _dynapyt_ast_ = _dynapyt_parse_to_ast_(_dynapyt_source_code_) 10 | try: 11 | def foo(): 12 | _call_(_dynapyt_ast_, 1, lambda: print('foo')) 13 | _call_(_dynapyt_ast_, 2, lambda: bar()) 14 | 15 | def bar(): 16 | _call_(_dynapyt_ast_, 3, lambda: print('bar')) 17 | 18 | def spam(): 19 | _call_(_dynapyt_ast_, 4, lambda: print('spam')) 20 | _call_(_dynapyt_ast_, 5, lambda: foo()) 21 | 22 | _call_(_dynapyt_ast_, 6, lambda: spam()) 23 | except Exception as _dynapyt_exception_: 24 | _catch_(_dynapyt_exception_) -------------------------------------------------------------------------------- /example_programs/targetPrograms/demo.py: -------------------------------------------------------------------------------- 1 | # DYNAPYT: DO NOT INSTRUMENT 2 | 3 | 4 | from dynapyt.runtime import _catch_ 5 | from dynapyt.runtime import _enter_if_ 6 | 7 | _dynapyt_ast_ = __file__ + ".orig" 8 | try: 9 | a = 0 10 | if _enter_if_(_dynapyt_ast_, 0, a < 10): 11 | print('small') 12 | else: 13 | print('large') 14 | except Exception as _dynapyt_exception_: 15 | _catch_(_dynapyt_exception_) 16 | -------------------------------------------------------------------------------- /example_programs/targetPrograms/keyInList.py: -------------------------------------------------------------------------------- 1 | # DYNAPYT: DO NOT INSTRUMENT 2 | 3 | # https://docs.quantifiedcode.com/python-anti-patterns/performance/using_key_in_list_to_check_if_key_is_contained_in_a_list.html 4 | 5 | 6 | from dynapyt.runtime import _catch_ 7 | from dynapyt.runtime import _comp_op_ 8 | 9 | _dynapyt_ast_ = __file__ + ".orig" 10 | try: 11 | l = [-i for i in range(1000)] 12 | 13 | res = 0 14 | 15 | for i in range(0, 2000, 7): 16 | if _comp_op_(_dynapyt_ast_, 0, -i, [(3, l)]): 17 | res += 1 18 | except Exception as _dynapyt_exception_: 19 | _catch_(_dynapyt_exception_) -------------------------------------------------------------------------------- /example_programs/targetPrograms/keyInSet.py: -------------------------------------------------------------------------------- 1 | # DYNAPYT: DO NOT INSTRUMENT 2 | 3 | # https://docs.quantifiedcode.com/python-anti-patterns/performance/using_key_in_list_to_check_if_key_is_contained_in_a_list.html 4 | 5 | 6 | from dynapyt.runtime import _catch_ 7 | from dynapyt.runtime import _comp_op_ 8 | 9 | _dynapyt_ast_ = __file__ + ".orig" 10 | try: 11 | l = set([-i for i in range(1000)]) 12 | 13 | res = 0 14 | 15 | for i in range(0, 2000, 7): 16 | if _comp_op_(_dynapyt_ast_, 0, -i, [(3, l)]): 17 | res += 1 18 | except Exception as _dynapyt_exception_: 19 | _catch_(_dynapyt_exception_) -------------------------------------------------------------------------------- /example_programs/targetPrograms/modifyListWhileIterating.py: -------------------------------------------------------------------------------- 1 | # inspired by https://www.toptal.com/python/top-10-mistakes-that-python-programmers-make 2 | 3 | if __name__ == "__main__": 4 | ''' 5 | Modifies a list while iterating over it, which is 6 | considered an anti-pattern in Python, but does not 7 | raise any exception. 8 | ''' 9 | l = [0, 1, 2, 3] 10 | for i in range(len(l)): 11 | print(i) 12 | if i % 2 == 0: 13 | print("appending") 14 | l.append(7) 15 | else: 16 | print("deleting") 17 | del l[i] 18 | print(l[i]) 19 | print("done") 20 | -------------------------------------------------------------------------------- /example_programs/targetPrograms/simpleProgram.py: -------------------------------------------------------------------------------- 1 | if __name__ == '__main__': 2 | a=23 3 | print(a) 4 | -------------------------------------------------------------------------------- /example_programs/targetPrograms/simpleProgram.py.orig: -------------------------------------------------------------------------------- 1 | if __name__ == '__main__': 2 | a=23 3 | print(a) 4 | -------------------------------------------------------------------------------- /example_programs/targetPrograms/tempTestMichaelToRemove.py.orig: -------------------------------------------------------------------------------- 1 | c = a + (x*y) -------------------------------------------------------------------------------- /example_programs/targetPrograms/unnecessaryDoubleDictQuery.py: -------------------------------------------------------------------------------- 1 | # inspired by https://docs.quantifiedcode.com/python-anti-patterns/correctness/not_using_get_to_return_a_default_value_from_a_dictionary.html 2 | 3 | if __name__ == '__main__': 4 | ''' 5 | Instead of checking if a key is in a dict, and then getting it, 6 | should use dict.get(key, default). 7 | ''' 8 | 9 | dictionary = {"message": "Hello, World!"} 10 | data = "" 11 | if "message" in dictionary: 12 | data = dictionary["message"] 13 | print(data) 14 | -------------------------------------------------------------------------------- /example_programs/targetPrograms/unnecessaryDoubleDictQuery.py.orig: -------------------------------------------------------------------------------- 1 | # inspired by https://docs.quantifiedcode.com/python-anti-patterns/correctness/not_using_get_to_return_a_default_value_from_a_dictionary.html 2 | 3 | if __name__ == '__main__': 4 | dictionary = {"message": "Hello, World!"} 5 | data = "" 6 | if "message" in dictionary: 7 | data = dictionary["message"] 8 | print(data) -------------------------------------------------------------------------------- /execution_invariance_test/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024-present Aryaz Eghbali 2 | # 3 | # SPDX-License-Identifier: MIT 4 | # DYNAPYT: DO NOT INSTRUMENT 5 | -------------------------------------------------------------------------------- /execution_invariance_test/analysis/LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024-present Keerthi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /execution_invariance_test/analysis/README.md: -------------------------------------------------------------------------------- 1 | # analysis 2 | 3 | [![PyPI - Version](https://img.shields.io/pypi/v/analysis.svg)](https://pypi.org/project/analysis) 4 | [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/analysis.svg)](https://pypi.org/project/analysis) 5 | 6 | ----- 7 | 8 | ## Table of Contents 9 | 10 | - [Installation](#installation) 11 | - [License](#license) 12 | 13 | ## Installation 14 | 15 | ```console 16 | pip install analysis 17 | ``` 18 | 19 | ## License 20 | 21 | `analysis` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license. 22 | -------------------------------------------------------------------------------- /execution_invariance_test/analysis/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "analysis" 7 | dynamic = ["version"] 8 | description = '' 9 | readme = "README.md" 10 | requires-python = ">=3.8" 11 | license = "MIT" 12 | keywords = [] 13 | authors = [ 14 | { name = "Keerthi", email = "keerthivasudevan98@gmail.com" }, 15 | ] 16 | classifiers = [ 17 | "Development Status :: 4 - Beta", 18 | "Programming Language :: Python", 19 | "Programming Language :: Python :: 3.8", 20 | "Programming Language :: Python :: 3.9", 21 | "Programming Language :: Python :: 3.10", 22 | "Programming Language :: Python :: 3.11", 23 | "Programming Language :: Python :: 3.12", 24 | "Programming Language :: Python :: Implementation :: CPython", 25 | "Programming Language :: Python :: Implementation :: PyPy", 26 | ] 27 | dependencies = [] 28 | 29 | [project.urls] 30 | Documentation = "https://github.com/Keerthi/analysis#readme" 31 | Issues = "https://github.com/Keerthi/analysis/issues" 32 | Source = "https://github.com/Keerthi/analysis" 33 | 34 | [tool.hatch.version] 35 | path = "src/analysis/__about__.py" 36 | 37 | [tool.hatch.envs.default] 38 | dependencies = [ 39 | "pytest-json-report", 40 | ] 41 | 42 | [tool.hatch.envs.types] 43 | extra-dependencies = [ 44 | "mypy>=1.0.0", 45 | ] 46 | [tool.hatch.envs.types.scripts] 47 | check = "mypy --install-types --non-interactive {args:src/analysis tests}" 48 | 49 | [tool.coverage.run] 50 | source_pkgs = ["analysis", "tests"] 51 | branch = true 52 | parallel = true 53 | omit = [ 54 | "src/analysis/__about__.py", 55 | ] 56 | 57 | [tool.coverage.paths] 58 | analysis = ["src/analysis", "*/analysis/src/analysis"] 59 | tests = ["tests", "*/analysis/tests"] 60 | 61 | [tool.coverage.report] 62 | exclude_lines = [ 63 | "no cov", 64 | "if __name__ == .__main__.:", 65 | "if TYPE_CHECKING:", 66 | ] 67 | -------------------------------------------------------------------------------- /execution_invariance_test/analysis/src/analysis/__about__.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024-present Keerthi 2 | # 3 | # SPDX-License-Identifier: MIT 4 | __version__ = "0.0.1" 5 | -------------------------------------------------------------------------------- /execution_invariance_test/analysis/src/analysis/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024-present Keerthi 2 | # 3 | # SPDX-License-Identifier: MIT 4 | -------------------------------------------------------------------------------- /execution_invariance_test/analysis/src/analysis/analysis.py: -------------------------------------------------------------------------------- 1 | 2 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 3 | 4 | 5 | class Analysis(BaseAnalysis): 6 | 7 | def runtime_event(self, dyn_ast: str, iid: int) -> None: 8 | pass -------------------------------------------------------------------------------- /execution_invariance_test/analysis/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024-present Keerthi 2 | # 3 | # SPDX-License-Identifier: MIT 4 | -------------------------------------------------------------------------------- /execution_invariance_test/projects/simple-test/LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024-present Keerthi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /execution_invariance_test/projects/simple-test/README.md: -------------------------------------------------------------------------------- 1 | # simple-test 2 | 3 | [![PyPI - Version](https://img.shields.io/pypi/v/simple-test.svg)](https://pypi.org/project/simple-test) 4 | [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/simple-test.svg)](https://pypi.org/project/simple-test) 5 | 6 | ----- 7 | 8 | ## Table of Contents 9 | 10 | - [Installation](#installation) 11 | - [License](#license) 12 | 13 | ## Installation 14 | 15 | ```console 16 | pip install simple-test 17 | ``` 18 | 19 | ## License 20 | 21 | `simple-test` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license. 22 | -------------------------------------------------------------------------------- /execution_invariance_test/projects/simple-test/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "simple-test" 7 | dynamic = ["version"] 8 | description = '' 9 | readme = "README.md" 10 | requires-python = ">=3.8" 11 | license = "MIT" 12 | keywords = [] 13 | authors = [ 14 | { name = "Keerthi", email = "keerthivasudevan98@gmail.com" }, 15 | ] 16 | classifiers = [ 17 | "Development Status :: 4 - Beta", 18 | "Programming Language :: Python", 19 | "Programming Language :: Python :: 3.8", 20 | "Programming Language :: Python :: 3.9", 21 | "Programming Language :: Python :: 3.10", 22 | "Programming Language :: Python :: 3.11", 23 | "Programming Language :: Python :: 3.12", 24 | "Programming Language :: Python :: Implementation :: CPython", 25 | "Programming Language :: Python :: Implementation :: PyPy", 26 | ] 27 | dependencies = [] 28 | 29 | [project.urls] 30 | Documentation = "https://github.com/Keerthi/simple-test#readme" 31 | Issues = "https://github.com/Keerthi/simple-test/issues" 32 | Source = "https://github.com/Keerthi/simple-test" 33 | 34 | [tool.hatch.version] 35 | path = "src/simple_test/__about__.py" 36 | 37 | [tool.hatch.envs.types] 38 | extra-dependencies = [ 39 | "mypy>=1.0.0", 40 | ] 41 | [tool.hatch.envs.types.scripts] 42 | check = "mypy --install-types --non-interactive {args:src/simple_test tests}" 43 | 44 | [tool.coverage.run] 45 | source_pkgs = ["simple_test", "tests"] 46 | branch = true 47 | parallel = true 48 | omit = [ 49 | "src/simple_test/__about__.py", 50 | ] 51 | 52 | [tool.coverage.paths] 53 | simple_test = ["src/simple_test", "*/simple-test/src/simple_test"] 54 | tests = ["tests", "*/simple-test/tests"] 55 | 56 | [tool.coverage.report] 57 | exclude_lines = [ 58 | "no cov", 59 | "if __name__ == .__main__.:", 60 | "if TYPE_CHECKING:", 61 | ] 62 | -------------------------------------------------------------------------------- /execution_invariance_test/projects/simple-test/src/simple_test/__about__.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024-present Aryaz Eghbali 2 | # 3 | # SPDX-License-Identifier: MIT 4 | # DYNAPYT: DO NOT INSTRUMENT 5 | __version__ = "0.0.1" 6 | -------------------------------------------------------------------------------- /execution_invariance_test/projects/simple-test/src/simple_test/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024-present Aryaz Eghbali 2 | # 3 | # SPDX-License-Identifier: MIT 4 | # DYNAPYT: DO NOT INSTRUMENT 5 | -------------------------------------------------------------------------------- /execution_invariance_test/projects/simple-test/src/simple_test/simple.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | import os.path 3 | 4 | def add_one(x: int) -> int: 5 | return x + 1 6 | 7 | 8 | def multiply_two(x: int) -> int: 9 | return x * 2 10 | 11 | 12 | def add_together(x: int, y: int) -> int: 13 | return x + y 14 | 15 | 16 | def get_stack(): 17 | return traceback.extract_stack() -------------------------------------------------------------------------------- /execution_invariance_test/projects/simple-test/tests/stack_test.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | from simple_test.simple import get_stack 3 | 4 | def test_stack(): 5 | trace = get_stack() 6 | trace_list = traceback.format_list(trace) 7 | trace_length = len(trace_list) 8 | expected_trace_str_length = 34 9 | assert trace_length == expected_trace_str_length 10 | 11 | -------------------------------------------------------------------------------- /execution_invariance_test/projects/simple-test/tests/test_simple.py: -------------------------------------------------------------------------------- 1 | from simple_test.simple import add_one, multiply_two, add_together 2 | 3 | 4 | def test_add_one_1(): 5 | assert add_one(1) == 2 6 | 7 | 8 | def test_add_one_2(): 9 | assert add_one(2) == 3 10 | 11 | 12 | def test_multiply_two_1(): 13 | assert multiply_two(1) == 2 14 | 15 | 16 | def test_multiply_two_2(): 17 | assert multiply_two(2) == 4 18 | 19 | 20 | def test_add_together_1_2(): 21 | assert add_together(1, 2) == 3 22 | 23 | 24 | def test_add_together_2_3(): 25 | assert add_together(2, 3) == 5 26 | -------------------------------------------------------------------------------- /execution_invariance_test/run_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | python3 -m venv venv 4 | source venv/bin/activate 5 | pip install -U pip setuptools 6 | SCRIPTS_DIR=$(dirname $(realpath "$0")) 7 | PARENT_DIR=$(dirname $SCRIPTS_DIR) 8 | ANALYSIS_DIR=$SCRIPTS_DIR/analysis 9 | pip install -e $ANALYSIS_DIR 10 | pip install pytest-json-report 11 | pip install $PARENT_DIR 12 | uniqueID=$(python -c "import uuid; print(uuid.uuid4())") 13 | export DYNAPYT_SESSION_ID=$uniqueID 14 | echo "DYNAPYT_SESSION_ID=$DYNAPYT_SESSION_ID" 15 | temp_dir="${TMPDIR:-/tmp}" 16 | file_path=$temp_dir/dynapyt_analyses-$uniqueID.txt 17 | touch $file_path 18 | echo "analysis.analysis.Analysis" > $file_path 19 | cat $file_path 20 | python $SCRIPTS_DIR/test.py 21 | deactivate 22 | rm $temp_dir/dynapyt_analyses-$uniqueID.txt 23 | rm -rf venv 24 | 25 | -------------------------------------------------------------------------------- /execution_invariance_test/test_folders.txt: -------------------------------------------------------------------------------- 1 | execution_invariance_test/projects/simple-test simple_test 2 | -------------------------------------------------------------------------------- /experiment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # $1: repo name 3 | # $2: test directory in repository root 4 | # $3: analysis name 5 | 6 | [ -d venvs ] || mkdir venvs 7 | [ -d under_test ] || mkdir under_test 8 | 9 | python -m pip install --user virtualenv 10 | 11 | # Create virtual environtment 12 | venv=$1_$3_env 13 | virtualenv --python 3.9.0 venvs/$venv 14 | source venvs/$venv/bin/activate 15 | 16 | export PYTHONPATH="$PWD/venvs/$venv/lib/python3.9/site-packages" 17 | 18 | exactTime=`date +%y%m%d%H%M%S` 19 | 20 | # Install DynaPyt 21 | pip install -r requirements.txt 22 | pip install . 23 | 24 | # Install requirements for package under test 25 | cp -r ./test/PythonRepos/$1 ./under_test/$1_$3 26 | cd ./under_test/$1_$3 27 | printf "import pytest\n\npytest.main(['-n', '8', '--import-mode=importlib'" > run_all_tests.py 28 | while read line; do 29 | printf ", '--deselect=$line'" >> run_all_tests.py 30 | done < ../$1_tmp/filtered.txt 31 | printf ", '$(pwd)/$2/'])\n" >> run_all_tests.py 32 | [ -f requirements.txt ] && pip install -r requirements.txt 33 | 34 | if [ $3 = "original" ]; then 35 | # Install package 36 | [ -f myInstall.sh ] && bash ./myInstall.sh || pip install . 37 | # Run tests 38 | python run_all_tests.py > $1_original_${exactTime}_output.txt 39 | tail -n 3 $1_original_${exactTime}_output.txt 40 | else 41 | # Instrument code 42 | python -m dynapyt.run_instrumentation --dir . --analysis $3 43 | # Install package 44 | [ -f myInstall.sh ] && bash ./myInstall.sh || pip install . 45 | # Run tests 46 | python -m dynapyt.run_analysis --entry run_all_tests.py --analysis $3 > $1_$3_${exactTime}_output.txt 47 | tail -n 3 $1_$3_${exactTime}_output.txt 48 | fi 49 | 50 | cd ../.. 51 | source deactivate 52 | rm -rf venvs/$venv 53 | -------------------------------------------------------------------------------- /extract_bad_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # $1: repo name 3 | # $2: test directory in repository root 4 | 5 | [ -d venvs ] || mkdir venvs 6 | [ -d under_test ] || mkdir under_test 7 | 8 | python -m pip install --user virtualenv 9 | 10 | # Create virtual environtment 11 | venv=$1_tmp_env 12 | virtualenv --python 3.9.0 venvs/$venv 13 | source venvs/$venv/bin/activate 14 | 15 | # Install requirements for package under test 16 | cp -r ./test/PythonRepos/$1 ./under_test/$1_tmp 17 | cp filter_tests.py ./under_test/$1_tmp 18 | cd ./under_test/$1_tmp 19 | 20 | pip install pytest 21 | 22 | [ -f requirements.txt ] && pip install -r requirements.txt 23 | 24 | # Install package 25 | [ -f myInstall.sh ] && bash ./myInstall.sh || pip install . 26 | # Run tests 27 | python filter_tests.py $(pwd) $2 28 | 29 | cd ../.. 30 | source deactivate 31 | rm -rf venvs/$venv 32 | -------------------------------------------------------------------------------- /filter_tests.py: -------------------------------------------------------------------------------- 1 | import linecache 2 | import sys 3 | import pytest 4 | 5 | bad_tests = set() 6 | results = [] 7 | print(' '.join(sys.argv)) 8 | test_dir = sys.argv[-1] 9 | 10 | def find_testfile(frame, package_test_dir): 11 | while frame is not None: 12 | if package_test_dir in frame.f_code.co_filename: 13 | ln = frame.f_lineno 14 | current_line = linecache.getline(frame.f_code.co_filename, ln) 15 | current_indent = len(current_line) - len(current_line.lstrip()) 16 | while ln > 0: 17 | ln -= 1 18 | this_line = linecache.getline(frame.f_code.co_filename, ln) 19 | if len(this_line) - len(this_line.lstrip()) < current_indent and this_line.lstrip().startswith('def '): 20 | bad_tests.add((frame.f_code.co_filename + '::' + linecache.getline(frame.f_code.co_filename, ln).strip()[4:].split('(')[0]).split('/')[-1]) 21 | break 22 | frame = frame.f_back 23 | 24 | def uses_stack(frame, event, arg=None): 25 | code = frame.f_code 26 | access_path_names = code.co_names 27 | file_name = code.co_filename 28 | 29 | if '.'.join(access_path_names) in ['sys._getframe', 'sys.exc_info', 'inspect.stack']: 30 | find_testfile(frame, test_dir) 31 | elif file_name.endswith('inspect.py') and any(map(lambda x: x in linecache.getline(file_name, frame.f_lineno), ['trace', 'stack', 'currentframe', 'getinnerframes', 'getouterframes', 'getlineno', 'getframeinfo'])): 32 | find_testfile(frame, test_dir) 33 | return uses_stack 34 | 35 | sys.settrace(uses_stack) 36 | 37 | pytest.main(sys.argv[1:]) 38 | 39 | print('\n'.join(bad_tests)) 40 | with open('filtered.txt', 'w') as f: 41 | f.write('\n'.join(bad_tests)) -------------------------------------------------------------------------------- /prepare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # $1: repo name 3 | # $2: test directory in repository root 4 | # $3: analysis name 5 | 6 | [ -d venvs ] || mkdir venvs 7 | [ -d under_test ] || mkdir under_test 8 | 9 | python -m pip install --user virtualenv 10 | 11 | # Create virtual environtment 12 | venv=$1_$3_env 13 | virtualenv --python 3.9.0 venvs/$venv 14 | source venvs/$venv/bin/activate -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "dynapyt" 7 | dynamic = ["version"] 8 | description = "Dynamic analysis framework for Python" 9 | readme = "README.md" 10 | license = "MIT" 11 | requires-python = ">=3.6" 12 | authors = [ 13 | { name = "Aryaz Eghbali", email = "aryaz.egh@gmail.com" }, 14 | { name = "Michael Pradel", email = "michael@binaervarianz.de" }, 15 | ] 16 | classifiers = [ 17 | "License :: OSI Approved :: MIT License", 18 | "Operating System :: OS Independent", 19 | "Programming Language :: Python :: 3", 20 | ] 21 | dependencies = [ 22 | "libcst", 23 | "filelock", 24 | ] 25 | 26 | [project.urls] 27 | "Bug Tracker" = "https://github.com/sola-st/DynaPyt/issues" 28 | Homepage = "https://github.com/sola-st/DynaPyt" 29 | 30 | [tool.hatch.version] 31 | path = "src/dynapyt/__init__.py" 32 | 33 | [tool.hatch.build.targets.wheel] 34 | packages = [ 35 | "src/dynapyt", 36 | ] 37 | 38 | [tool.hatch.build.targets.wheel.force-include] 39 | "src/dynapyt/utils/hierarchy.json" = "src/dynapyt/utils/hierarchy.json" 40 | 41 | [tool.hatch.build.targets.sdist] 42 | include = [ 43 | "/src", 44 | ] 45 | 46 | [project.optional-dependencies] 47 | end2end = [ 48 | "pytest-xdist", 49 | "fire", 50 | ] 51 | 52 | [tool.hatch.envs.end2end] 53 | dependencies = [ 54 | "pytest", 55 | "fire", 56 | ] 57 | [tool.hatch.envs.end2end.scripts] 58 | run = "bash end2end_tests/scripts/run_single_project.sh simple-with-pytest simple_with_pytest tests" 59 | cli = "bash end2end_tests/scripts/run_cli.sh simple-with-pytest simple_with_pytest \"pytest -n 6 tests\"" 60 | 61 | [tool.hatch.envs.exec_invariance_test] 62 | dependencies = [ 63 | "pytest-json-report", 64 | ] 65 | [tool.hatch.envs.exec_invariance_test.scripts] 66 | run = "bash execution_invariance_test/run_test.sh" 67 | 68 | [tool.black] 69 | extend-exclude = ''' 70 | ( 71 | ^/tests/trace_single_hook/write/program.py 72 | | ^/tests/regression/assert_no_space/program.py 73 | ) 74 | ''' 75 | 76 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | importlib-resources 2 | libcst 3 | pytest 4 | pytest-xdist 5 | filelock -------------------------------------------------------------------------------- /run_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # $1: repo name 3 | # $2: test directory in repository root 4 | # $3: analysis name 5 | cd ./test/PythonRepos/$1 6 | #exactTime=`date +%y%m%d%H%M%S` 7 | 8 | printf "import pytest\n\npytest.main(['-n', '8', '--import-mode=importlib', '$(pwd)/$2/'])\n" > run_all_tests.py 9 | [ -f requirements.txt ] && pip install --user -r requirements.txt 10 | 11 | if [ $3 = "original" ]; then 12 | [ -f myInstall.sh ] && bash ./myInstall.sh || pip install --user . 13 | python run_all_tests.py #> ../../results/$1_original_${exactTime}_output.txt 14 | else 15 | python -m dynapyt.run_instrumentation --dir . --analysis $3 16 | pip show dynapyt 17 | [ -f myInstall.sh ] && bash ./myInstall.sh || pip install --user . 18 | 19 | python -m dynapyt.run_analysis --entry run_all_tests.py --analysis $3 #> ../../results/$1_$2_${exactTime}_output.txt 20 | fi 21 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open("README.md", "r", encoding="utf-8") as fh: 4 | long_description = fh.read() 5 | 6 | setuptools.setup( 7 | name="dynapyt", 8 | version="0.2.3", 9 | author="Aryaz Eghbali", 10 | author_email="aryaz.egh@gmail.com", 11 | description="Dynamic analysis framework for Python", 12 | long_description=long_description, 13 | long_description_content_type="text/markdown", 14 | url="https://github.com/sola-st/DynaPyt", 15 | project_urls={ 16 | "Bug Tracker": "https://github.com/sola-st/DynaPyt/issues", 17 | }, 18 | classifiers=[ 19 | "Programming Language :: Python :: 3", 20 | "License :: OSI Approved :: MIT License", 21 | "Operating System :: OS Independent", 22 | ], 23 | package_dir={"": "src", "test": "test"}, 24 | packages=setuptools.find_packages(where="src"), 25 | package_data={ 26 | "dynapyt": ["utils/hierarchy.json"], 27 | }, 28 | include_package_data=True, 29 | python_requires=">=3.6", 30 | setup_requires=[ 31 | "libcst", 32 | ], 33 | install_requires=[ 34 | "libcst", 35 | ], 36 | ) 37 | -------------------------------------------------------------------------------- /src/dynapyt/__init__.py: -------------------------------------------------------------------------------- 1 | """DynaPyt""" 2 | __version__ = "2.2.0" 3 | -------------------------------------------------------------------------------- /src/dynapyt/analyses/BaseAnalysis.py: -------------------------------------------------------------------------------- 1 | import libcst as cst 2 | import os.path as path 3 | from ..instrument.IIDs import IIDs, Location 4 | 5 | 6 | class BaseAnalysis: 7 | 8 | def __init__(self, conf: str = None, output_dir: str = None) -> None: 9 | self.asts = {} 10 | self.conf = conf 11 | self.output_dir = output_dir 12 | 13 | def _get_ast(self, filepath: str) -> cst.CSTNodeT: 14 | if not path.exists(filepath): 15 | return None 16 | if filepath not in self.asts: 17 | src = "" 18 | with open(filepath, "r") as file: 19 | src = file.read() 20 | iids = IIDs(filepath) 21 | self.asts[filepath] = (cst.parse_module(src), iids) 22 | 23 | return self.asts[filepath] 24 | 25 | def iid_to_location(self, filepath: str, iid: int) -> Location: 26 | return IIDs(filepath).iid_to_location[iid] 27 | 28 | def location_to_iid(self, filepath: str, location: Location) -> int: 29 | return IIDs(filepath).location_to_iid[location] 30 | -------------------------------------------------------------------------------- /src/dynapyt/analyses/BranchCoverage.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from .BaseAnalysis import BaseAnalysis 3 | 4 | class BranchCoverage(BaseAnalysis): 5 | def __init__(self, **kwargs): 6 | super().__init__(**kwargs) 7 | self.branches = dict() 8 | 9 | def enter_control_flow(self, dyn_ast: str, iid: int, cond_value: bool) -> Optional[bool]: 10 | self.branches[(iid, bool(cond_value))] = self.branches.get((iid, bool(cond_value)), 0) + 1 11 | 12 | def end_execution(self): 13 | for k, v in self.branches.items(): 14 | print(f'Branch {k[0]} taken with condition {k[1]}, {v} time{"" if v == 1 else "s"}') -------------------------------------------------------------------------------- /src/dynapyt/analyses/Demo.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from .BaseAnalysis import BaseAnalysis 3 | 4 | class Demo(BaseAnalysis): 5 | def __init__(self, **kwargs): 6 | super().__init__(**kwargs) 7 | self.branches = dict() 8 | 9 | def enter_control_flow(self, dyn_ast: str, iid: int, cond_value: bool) -> Optional[bool]: 10 | self.branches[(iid, bool(cond_value))] = self.branches.get((iid, bool(cond_value)), 0) + 1 11 | return False 12 | 13 | def end_execution(self): 14 | for k, v in self.branches.items(): 15 | print(f'Branch {k[0]} taken with condition {k[1]}, {v} time{"" if v == 1 else "s"}') 16 | -------------------------------------------------------------------------------- /src/dynapyt/analyses/EventAnalysis.py: -------------------------------------------------------------------------------- 1 | from .BaseAnalysis import BaseAnalysis 2 | 3 | class EventAnalysis(BaseAnalysis): 4 | def __init__(self, **kwargs) -> None: 5 | super().__init__(**kwargs) 6 | 7 | def runtime_event(self, dyn_ast, iid): 8 | print('.', end='') -------------------------------------------------------------------------------- /src/dynapyt/analyses/KeyInListAnalysis.py: -------------------------------------------------------------------------------- 1 | # https://docs.quantifiedcode.com/python-anti-patterns/performance/using_key_in_list_to_check_if_key_is_contained_in_a_list.html 2 | from .BaseAnalysis import BaseAnalysis 3 | 4 | class KeyInListAnalysis(BaseAnalysis): 5 | def __init__(self, **kwargs): 6 | super().__init__(**kwargs) 7 | self.threshold = 100 8 | 9 | def _in(self, dyn_ast, iid, left, right, result): 10 | if isinstance(right, list) and len(right) > self.threshold: 11 | print('Dynapyt warning: checking key in list is less efficient than checking key in set') 12 | 13 | def not_in(self, dyn_ast, iid, left, right, result): 14 | if isinstance(right, list) and len(right) > self.threshold: 15 | print('Dynapyt warning: checking key in list is less efficient than checking key in set') 16 | -------------------------------------------------------------------------------- /src/dynapyt/analyses/LiteralAnalysis.py: -------------------------------------------------------------------------------- 1 | from .BaseAnalysis import BaseAnalysis 2 | from typing import Any 3 | 4 | class LiteralAnalysis(BaseAnalysis): 5 | def __init__(self, **kwargs) -> None: 6 | super().__init__(**kwargs) 7 | 8 | def literal(self, dyn_ast: str, iid: int, value: Any) -> Any: 9 | print(str(iid) + ': Literal') 10 | -------------------------------------------------------------------------------- /src/dynapyt/analyses/MLMemoryAnalysis.py: -------------------------------------------------------------------------------- 1 | # Inspired by https://pytorch.org/docs/stable/notes/faq.html#my-model-reports-cuda-runtime-error-2-out-of-memory 2 | from collections import defaultdict 3 | from .BaseAnalysis import BaseAnalysis 4 | 5 | class MLMemoryAnalysis(BaseAnalysis): 6 | def __init__(self, **kwargs) -> None: 7 | super().__init__(**kwargs) 8 | self.in_ctrl_flow = [] 9 | self.threshold = 3 10 | self.memory_leak = defaultdict(lambda: 0) 11 | self.last_opr = None 12 | 13 | def enter_control_flow(self, dyn_ast, iid, condition): 14 | self.last_opr = None 15 | if (len(self.in_ctrl_flow) > 0) and (self.in_ctrl_flow[-1] != iid): 16 | self.in_ctrl_flow.append(iid) 17 | 18 | def exit_control_flow(self, dyn_ast, iid): 19 | self.last_opr = None 20 | self.in_ctrl_flow.pop() 21 | 22 | def binary_operation(self, dyn_ast, iid, opr, left, right, res): 23 | if (len(self.in_ctrl_flow) > 0) and right.requires_grad: 24 | self.last_opr = iid 25 | else: 26 | self.last_opr = None 27 | 28 | def write(self, dyn_ast, iid, left, right): 29 | if (len(self.in_ctrl_flow) > 0) and right.requires_grad and (self.last_opr is not None): 30 | cur = (iid, self.in_ctrl_flow[-1]) 31 | self.memory_leak[cur] += 1 32 | if self.memory_leak[cur] > 3: 33 | print('Memory issue detected') 34 | exit(1) 35 | self.last_opr = None -------------------------------------------------------------------------------- /src/dynapyt/analyses/ManipulateExec.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Any 2 | from .BaseAnalysis import BaseAnalysis 3 | from random import random 4 | 5 | class ManipulateExec(BaseAnalysis): 6 | def enter_if(self, f: str, iid: int, cond_value: bool) -> Optional[bool]: 7 | if random() < 0.5: 8 | return not cond_value 9 | 10 | def write(self, f: str, iid: int, old_vals: Any, new_val: Any) -> Any: 11 | if new_val == 23: 12 | return 42 -------------------------------------------------------------------------------- /src/dynapyt/analyses/MemoryAccessAnalysis.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from .BaseAnalysis import BaseAnalysis 3 | from typing import Any 4 | 5 | class MemoryAccessAnalysis(BaseAnalysis): 6 | def __init__(self, **kwargs) -> None: 7 | super().__init__(**kwargs) 8 | self.danger_of_recursion = False 9 | logging.basicConfig(filename='output.log', format='%(message)s', encoding='utf-8', level=logging.INFO) 10 | 11 | def log(self, iid: int, *args): 12 | res = '' 13 | for arg in args: 14 | if self.danger_of_recursion: 15 | res += ' ' + str(hex(id(arg))) 16 | else: 17 | res += ' ' + str(arg) 18 | logging.info(str(iid) + ': ' + res[:80]) 19 | 20 | def memory_access(self, dyn_ast: str, iid: int, value: Any) -> Any: 21 | self.log(iid, 'Accessing memory:', value) 22 | -------------------------------------------------------------------------------- /src/dynapyt/analyses/OnlyAddAnalysis.py: -------------------------------------------------------------------------------- 1 | from .BaseAnalysis import BaseAnalysis 2 | 3 | class OnlyAddAnalysis(BaseAnalysis): 4 | def __init__(self, **kwargs) -> None: 5 | super().__init__(**kwargs) 6 | 7 | def add(self, dyn_ast, iid, left, opr, right): 8 | print(str(iid) + ': Add') -------------------------------------------------------------------------------- /src/dynapyt/analyses/OperationAnalysis.py: -------------------------------------------------------------------------------- 1 | from .BaseAnalysis import BaseAnalysis 2 | from typing import Any, List 3 | 4 | class OperationAnalysis(BaseAnalysis): 5 | def __init__(self, **kwargs) -> None: 6 | super().__init__(**kwargs) 7 | 8 | def operation(self, dyn_ast: str, iid: int, operator: str, operands: List[Any], result: Any) -> Any: 9 | print(str(iid) + ': Operation') 10 | -------------------------------------------------------------------------------- /src/dynapyt/analyses/UnnecessaryDoubleDictQuery.py: -------------------------------------------------------------------------------- 1 | from typing import Any, List, Optional, Tuple 2 | from .BaseAnalysis import BaseAnalysis 3 | 4 | class UnnecessaryDoubleDictQuery(BaseAnalysis): 5 | 6 | # still incomplete 7 | 8 | def binary_op(self, iid: int, op: str, left: Any, right: Any, result: Any) -> Any: 9 | # TODO: doesn't seem to work on "x in y" 10 | print(f"{op} {left} {right}") 11 | -------------------------------------------------------------------------------- /src/dynapyt/analyses/__init__.py: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------------------------------- /src/dynapyt/instrument/IIDs.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | from os import path 3 | import json 4 | 5 | 6 | Location = namedtuple( 7 | "Location", ["file", "start_line", "start_column", "end_line", "end_column"] 8 | ) 9 | 10 | 11 | class IIDs: 12 | def __init__(self, file_path): 13 | if file_path.endswith(".py.orig"): 14 | file_path = file_path[:-8] + "-dynapyt.json" 15 | else: 16 | file_path = file_path[:-3] + "-dynapyt.json" 17 | if not path.exists(file_path): 18 | with open(file_path, "w") as f: 19 | json.dump({"next_iid": 0, "iid_to_location": {}}, f) 20 | self.next_iid = 0 21 | self.iid_to_location = {} 22 | self.location_to_iid = {} 23 | else: 24 | with open(file_path, "r") as file: 25 | json_object = json.load(file) 26 | self.next_iid = json_object["next_iid"] 27 | self.iid_to_location = { 28 | int(k): Location(**v) for k, v in json_object["iid_to_location"].items() 29 | } 30 | self.location_to_iid = { 31 | Location(*v): k for k, v in self.iid_to_location.items() 32 | } 33 | self.file_path = file_path 34 | 35 | def new(self, file, start_line, start_column, end_line, end_column): 36 | this_location = Location(file, start_line, start_column, end_line, end_column) 37 | if this_location in self.location_to_iid: 38 | return self.location_to_iid[this_location] 39 | self.iid_to_location[self.next_iid] = this_location 40 | self.next_iid += 1 41 | return self.next_iid - 1 42 | 43 | def store(self): 44 | all_data = { 45 | "next_iid": self.next_iid, 46 | "iid_to_location": dict( 47 | map( 48 | lambda item: (item[0], item[1]._asdict()), 49 | self.iid_to_location.items(), 50 | ) 51 | ), 52 | } 53 | json_object = json.dumps(all_data, indent=2) 54 | with open(self.file_path, "w") as file: 55 | file.write(json_object) 56 | -------------------------------------------------------------------------------- /src/dynapyt/instrument/__init__.py: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------------------------------- /src/dynapyt/instrument/filters.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List 2 | import functools 3 | 4 | SEPERATOR = "" 5 | START = "DynaPyt internal:" 6 | END = ":DynaPyt internal" 7 | 8 | 9 | def only(patterns=[]): 10 | def decorator_only(func): 11 | if len(patterns) > 0: 12 | if func.__doc__ is None: 13 | func.__doc__ = "" 14 | func.__doc__ += f"{START} only -> {SEPERATOR.join(patterns)} {END}" 15 | 16 | @functools.wraps(func) 17 | def only_wrapper(*args, **kwargs): 18 | return func(*args, **kwargs) 19 | 20 | return only_wrapper 21 | 22 | return decorator_only 23 | 24 | 25 | def ignore(patterns=[]): 26 | def decorator_ignore(func): 27 | if len(patterns) > 0: 28 | if func.__doc__ is None: 29 | func.__doc__ = "" 30 | func.__doc__ += f"{START} ignore -> {SEPERATOR.join(patterns)} {END}" 31 | 32 | @functools.wraps(func) 33 | def ignore_wrapper(*args, **kwargs): 34 | return func(*args, **kwargs) 35 | 36 | return ignore_wrapper 37 | 38 | return decorator_ignore 39 | 40 | 41 | def get_details(func) -> Dict[str, List[str]]: 42 | if func.__doc__ is None: 43 | return {} 44 | start = func.__doc__.find(START) 45 | if start == -1: 46 | return {} 47 | end = func.__doc__.find(END) 48 | if end == -1: 49 | return {} 50 | specs = func.__doc__[start + len(START) : end] 51 | details = {} 52 | if specs.strip().startswith("only ->"): 53 | details["only"] = specs.strip().split(" -> ")[1].split(SEPERATOR) 54 | elif specs.strip().startswith("ignore ->"): 55 | details["ignore"] = specs.strip().split(" -> ")[1].split(SEPERATOR) 56 | return details 57 | -------------------------------------------------------------------------------- /src/dynapyt/post_run.py: -------------------------------------------------------------------------------- 1 | import fire 2 | from pathlib import Path 3 | 4 | from .utils.runtimeUtils import gather_coverage, gather_output 5 | 6 | 7 | def post_run(coverage_dir: str = "", output_dir: str = ""): 8 | if len(output_dir) > 0: 9 | gather_output(Path(output_dir)) 10 | if len(coverage_dir) > 0: 11 | gather_coverage(Path(coverage_dir)) 12 | 13 | 14 | if __name__ == "__main__": 15 | fire.Fire(post_run) 16 | -------------------------------------------------------------------------------- /src/dynapyt/run_all.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from subprocess import run 3 | from os import walk 4 | from os import path 5 | import time 6 | from multiprocessing import Pool 7 | 8 | 9 | def process_files(cmd_list, file_path): 10 | comp_proc = run(cmd_list) 11 | if comp_proc.returncode != 0: 12 | print("Error at", file_path) 13 | exit(0) 14 | 15 | 16 | if __name__ == "__main__": 17 | parser = argparse.ArgumentParser() 18 | parser.add_argument( 19 | "--directory", help="Directory of the project to analyze", required=True 20 | ) 21 | parser.add_argument("--entry", help="Entry module of the execution", required=True) 22 | parser.add_argument( 23 | "--analysis", 24 | help="Analysis class(es) (full dotted path)", 25 | nargs="+", 26 | required=True, 27 | ) 28 | parser.add_argument( 29 | "--skip-instrumentation", help="Skip instrumentation", action="store_true" 30 | ) 31 | parser.add_argument( 32 | "--time-limit", help="Time limit for instrumentation in minutes" 33 | ) 34 | args = parser.parse_args() 35 | start = args.directory 36 | analysis = args.analysis 37 | entry = args.entry 38 | start_time = time.time() 39 | all_cmds = [] 40 | 41 | if args.skip_instrumentation != True: 42 | for dir_path, dir_names, file_names in walk(start): 43 | if (args.time_limit is not None) and ( 44 | (time.time() - start_time) / 60 > int(args.time_limit) 45 | ): 46 | break 47 | for name in file_names: 48 | if name.endswith(".py"): 49 | file_path = path.join(dir_path, name) 50 | cmd_list = [ 51 | "python", 52 | "-m", 53 | "dynapyt.instrument.instrument", 54 | "--files", 55 | file_path, 56 | "--analysis", 57 | ] + analysis 58 | all_cmds.append((cmd_list, file_path)) 59 | with Pool() as p: 60 | p.starmap(process_files, all_cmds) 61 | 62 | run( 63 | ["python", "-m", "dynapyt.run_analysis", "--entry", entry, "--analysis"] 64 | + analysis 65 | ) 66 | -------------------------------------------------------------------------------- /src/dynapyt/run_instrumentation.py: -------------------------------------------------------------------------------- 1 | from typing import List, Set 2 | import argparse 3 | from pathlib import Path 4 | import shutil 5 | from subprocess import run 6 | import time 7 | from dynapyt.instrument.instrument import instrument_files 8 | 9 | 10 | def instrument_dir( 11 | directory: str, 12 | analysis: List[str], 13 | use_external_dir: bool = False, 14 | exclude: Set[str] = set(), 15 | ): 16 | start_time = time.time() 17 | start = directory 18 | 19 | if isinstance(analysis, str) and Path(analysis).exists(): 20 | with open(analysis, "r") as f: 21 | analysis = [ana.strip() for ana in f.read().split("\n")] 22 | 23 | if use_external_dir: 24 | external_path = Path(start) / "dynapyt_analysis" 25 | # create new folder /dynapyt_analysis on same level as specified directory 26 | shutil.rmtree(external_path, ignore_errors=True) 27 | shutil.copytree(start, external_path) 28 | start = str(external_path) 29 | 30 | files = [str(f.resolve()) for f in Path(start).rglob("*.py") if f not in exclude] 31 | instrument_files(files, analysis) 32 | print("#################### Instrumentation took " + str(time.time() - start_time)) 33 | 34 | 35 | def process_files(cmd_list, file_path): 36 | comp_proc = run(cmd_list) 37 | if comp_proc.returncode != 0: 38 | print("Error at", file_path) 39 | 40 | 41 | if __name__ == "__main__": 42 | parser = argparse.ArgumentParser() 43 | parser.add_argument( 44 | "--directory", help="Directory of the project to analyze", required=True 45 | ) 46 | parser.add_argument( 47 | "--analysis", 48 | help="Analysis class(es) (full dotted path)", 49 | nargs="+", 50 | ) 51 | parser.add_argument( 52 | "--analysisFile", 53 | help="Analysis file with list of analysis classes", 54 | ) 55 | parser.add_argument( 56 | "--external_dir", 57 | help="Place instrumented files in another directory", 58 | dest="external_dir", 59 | action="store_true", 60 | ) 61 | args = parser.parse_args() 62 | start = args.directory 63 | analysis = args.analysis 64 | if args.analysisFile: 65 | analysis = args.analysisFile 66 | use_external_dir = args.external_dir 67 | instrument_dir(start, analysis, use_external_dir) 68 | -------------------------------------------------------------------------------- /src/dynapyt/utils/AnalysisUtils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Helper functions useful when writing analyses. 3 | """ 4 | 5 | from typing import Callable, NewType 6 | 7 | UndefinedValueType = NewType("UndefinedValueType", str) 8 | undefined_value = UndefinedValueType("undefined_value") 9 | 10 | 11 | def get_old_value(value_as_lambda: Callable) -> bool: 12 | """ 13 | Given a lambda function that represents an old value (just before a write), 14 | return the value or the special value `undefined_value`. 15 | """ 16 | try: 17 | v = value_as_lambda() 18 | return v 19 | except (NameError, KeyError): 20 | return undefined_value 21 | -------------------------------------------------------------------------------- /src/dynapyt/utils/__init__.py: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------------------------------- /src/nativetracer/__init__.py: -------------------------------------------------------------------------------- 1 | # -------------------------------------------------------------------------------- /src/nativetracer/trc.py: -------------------------------------------------------------------------------- 1 | import opcode 2 | def _trace_opcodes_(frame, event, arg=None): 3 | if event == 'opcode': 4 | code = frame.f_code 5 | offset = frame.f_lasti 6 | # print(f"{opcode.opname[code.co_code[offset]]:<18} | {frame.f_lineno}") 7 | else: 8 | #print('x') 9 | frame.f_trace_opcodes = True 10 | return _trace_opcodes_ 11 | 12 | def _trace_assignments_(frame, event, arg=None): 13 | if event == 'line': 14 | print(dir(frame)) 15 | print(dir(frame.f_code)) 16 | print(frame.f_globals) 17 | print(frame.f_lasti) 18 | print(frame.f_lineno) 19 | print(frame.f_locals) 20 | for i in dir(frame.f_code): 21 | if i.startswith('co_'): 22 | print(i, getattr(frame.f_code, i)) 23 | x = 0 24 | y = frame 25 | else: 26 | x = 1 27 | y = event 28 | z = x 29 | yy = y 30 | return _trace_assignments_ 31 | 32 | def _trace_all_(frame, event, arg=None): 33 | print(event) 34 | print(dir(frame)) 35 | print(dir(frame.f_code)) 36 | print(frame.f_globals) 37 | print(frame.f_lasti) 38 | print(frame.f_lineno) 39 | print(frame.f_locals) 40 | for i in dir(frame.f_code): 41 | if i.startswith('co_'): 42 | print(i, getattr(frame.f_code, i)) 43 | return _trace_all_ -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # Tests 2 | 3 | This directory contains *micro-tests* that each consist of 4 | * A small example program, stored in a file called `program.py` 5 | * A simple analysis, stored in a file called `analysis.py`, that uses a small set of hooks to either analyze or manipulate the program's execution 6 | * A file called `expected.txt` with the console output expected to be produced when `analysis.py` is applied to `program.py` 7 | 8 | Each micro-test is stored in a separate subdirectory of this directory. 9 | 10 | Run all tests with: 11 | ``` 12 | pytest tests 13 | ``` 14 | 15 | Run tests on a sub-directory with: 16 | ``` 17 | pytest --only= tests 18 | ``` -------------------------------------------------------------------------------- /tests/cleanup.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import shutil 3 | 4 | here = Path(__file__).parent.resolve() 5 | dirty_files = here.glob("**/*.py.orig") 6 | for dirty_file in dirty_files: 7 | metadata_file = Path(*(dirty_file.parts[:-1])) / ( 8 | dirty_file.name.rstrip(".py.orig") + "-dynapyt.json" 9 | ) 10 | print(f"Deleting {metadata_file}") 11 | if metadata_file.exists(): 12 | metadata_file.unlink() 13 | if (Path(*(dirty_file.parts[:-1])) / "__pycache__").exists(): 14 | print(f"Deleting {Path(*(dirty_file.parts[:-1])) / '__pycache__'}") 15 | shutil.rmtree(Path(*(dirty_file.parts[:-1])) / "__pycache__") 16 | correct_file = Path(*(dirty_file.parts[:-1])) / ( 17 | dirty_file.name.rstrip(".py.orig") + ".py" 18 | ) 19 | print(f"Restoring {dirty_file} to {correct_file}") 20 | dirty_file.rename(correct_file) 21 | 22 | dirty_files = here.glob("**/*-dynapyt.json") 23 | for dirty_file in dirty_files: 24 | if (Path(*(dirty_file.parts[:-1])) / "__pycache__").exists(): 25 | print(f"Deleting {Path(*(dirty_file.parts[:-1])) / '__pycache__'}") 26 | shutil.rmtree(Path(*(dirty_file.parts[:-1])) / "__pycache__") 27 | if dirty_file.exists(): 28 | dirty_file.unlink() 29 | 30 | dirty_files = here.glob("**/coverage*.json") 31 | for dirty_file in dirty_files: 32 | if dirty_file.exists(): 33 | dirty_file.unlink() 34 | 35 | dirty_dirs = here.glob("**/dynapyt_coverage-*") 36 | for dirty_dir in dirty_dirs: 37 | if dirty_dir.exists(): 38 | shutil.rmtree(dirty_dir) 39 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | from os import walk 2 | from os.path import realpath, dirname, sep 3 | 4 | 5 | def pytest_addoption(parser): 6 | parser.addoption( 7 | "--only", 8 | action="store", 9 | default=None, 10 | help="Run only the test in the specified directory", 11 | ) 12 | 13 | 14 | def pytest_generate_tests(metafunc): 15 | # find all subdirectories that contain a micro-test 16 | directories = [] 17 | selection = metafunc.config.getoption("only", default=None, skip=False) 18 | if selection is not None: 19 | start_dir = realpath(selection) 20 | current_dir = dirname(realpath(__file__)) 21 | if selection is None: 22 | start_dir = current_dir 23 | test_ids = [] 24 | for root, dirs, files in walk(start_dir): 25 | if all([f in files for f in ["program.py", "analysis.py", "expected.txt"]]): 26 | relative_path = root[len(current_dir) + len(sep) :] 27 | directories.append([root, relative_path]) 28 | test_ids.append(relative_path) 29 | 30 | # invoke the test in each directory 31 | metafunc.parametrize("directory_pair", directories, ids=test_ids) 32 | -------------------------------------------------------------------------------- /tests/filters/bool_literal_ignore/analysis.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 3 | from dynapyt.instrument.filters import ignore 4 | 5 | 6 | class TestAnalysis(BaseAnalysis): 7 | def begin_execution(self) -> None: 8 | print("begin execution") 9 | 10 | @ignore(patterns=["False"]) 11 | def boolean(self, dyn_ast: str, iid: int, val: Any) -> Any: 12 | print(f"boolean literal {val}") 13 | 14 | def end_execution(self) -> None: 15 | print("end execution") 16 | -------------------------------------------------------------------------------- /tests/filters/bool_literal_ignore/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | boolean literal True 3 | False 4 | end execution -------------------------------------------------------------------------------- /tests/filters/bool_literal_ignore/program.py: -------------------------------------------------------------------------------- 1 | x = True 2 | y = 2 == 2 3 | z = False 4 | print(z) 5 | -------------------------------------------------------------------------------- /tests/filters/call/analysis.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Dict, Tuple, Any 2 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 3 | from dynapyt.instrument.filters import only 4 | 5 | 6 | class TestAnalysis(BaseAnalysis): 7 | def begin_execution(self) -> None: 8 | print("begin execution") 9 | 10 | @only(patterns=["foo"]) 11 | def pre_call( 12 | self, dyn_ast: str, iid: int, function: Callable, pos_args: Tuple, kw_args: Dict 13 | ) -> None: 14 | print(f"pre call of {function.__name__}") 15 | 16 | @only(patterns=["foo"]) 17 | def post_call( 18 | self, 19 | dyn_ast: str, 20 | iid: int, 21 | result: Any, 22 | call: Callable, 23 | pos_args: Tuple, 24 | kw_args: Dict, 25 | ) -> Any: 26 | print(f"post call of {call.__name__}") 27 | 28 | def end_execution(self) -> None: 29 | print("end execution") 30 | -------------------------------------------------------------------------------- /tests/filters/call/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | pre call of foo 3 | hello world 4 | post call of foo 5 | 6 6 | pre call of foo 7 | 8 8 | post call of foo 9 | end execution -------------------------------------------------------------------------------- /tests/filters/call/program.py: -------------------------------------------------------------------------------- 1 | class X: 2 | def foo(self, x): 3 | print(x + x) 4 | 5 | 6 | def foo(x): 7 | print(x) 8 | 9 | 10 | def bar(x): 11 | print(2 * x) 12 | 13 | 14 | foo("hello world") 15 | bar(3) 16 | x = X() 17 | x.foo(4) 18 | -------------------------------------------------------------------------------- /tests/filters/int_literal_ignore/analysis.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 3 | from dynapyt.instrument.filters import ignore 4 | 5 | 6 | class TestAnalysis(BaseAnalysis): 7 | def begin_execution(self) -> None: 8 | print("begin execution") 9 | 10 | @ignore(patterns=["1"]) 11 | def integer(self, dyn_ast: str, iid: int, val: Any) -> Any: 12 | print(f"integer literal {val}") 13 | 14 | def end_execution(self) -> None: 15 | print("end execution") 16 | -------------------------------------------------------------------------------- /tests/filters/int_literal_ignore/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | integer literal 2 3 | integer literal 3 4 | 3 5 | end execution -------------------------------------------------------------------------------- /tests/filters/int_literal_ignore/program.py: -------------------------------------------------------------------------------- 1 | x = 1 2 | y = 2 3 | z = 3 4 | print(z) 5 | -------------------------------------------------------------------------------- /tests/filters/int_literal_only/analysis.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 3 | from dynapyt.instrument.filters import only 4 | 5 | 6 | class TestAnalysis(BaseAnalysis): 7 | def begin_execution(self) -> None: 8 | print("begin execution") 9 | 10 | @only(patterns=["1"]) 11 | def integer(self, dyn_ast: str, iid: int, val: Any) -> Any: 12 | print(f"integer literal {val}") 13 | 14 | def end_execution(self) -> None: 15 | print("end execution") 16 | -------------------------------------------------------------------------------- /tests/filters/int_literal_only/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | integer literal 1 3 | 3 4 | end execution -------------------------------------------------------------------------------- /tests/filters/int_literal_only/program.py: -------------------------------------------------------------------------------- 1 | x = 1 2 | y = 2 3 | z = 3 4 | print(z) 5 | -------------------------------------------------------------------------------- /tests/filters/read_ignore/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | from dynapyt.instrument.filters import ignore 3 | 4 | 5 | class TestAnalysis(BaseAnalysis): 6 | def begin_execution(self) -> None: 7 | print("begin execution") 8 | 9 | @ignore(patterns=["a"]) 10 | def read(self, dyn_ast, iid, value): 11 | print(f"read value {value}") 12 | 13 | def end_execution(self) -> None: 14 | print("end execution") 15 | -------------------------------------------------------------------------------- /tests/filters/read_ignore/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | 10 3 | read value 20 4 | 20 5 | read value 20 6 | 30 7 | end execution -------------------------------------------------------------------------------- /tests/filters/read_ignore/program.py: -------------------------------------------------------------------------------- 1 | a = 10 2 | b = 20 3 | print(a) 4 | print(b) 5 | print(a + b) 6 | -------------------------------------------------------------------------------- /tests/filters/read_ignore/skip.txt: -------------------------------------------------------------------------------- 1 | TODO: Add support for filtering based on variable name -------------------------------------------------------------------------------- /tests/filters/read_only/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | from dynapyt.instrument.filters import only 3 | 4 | 5 | class TestAnalysis(BaseAnalysis): 6 | def begin_execution(self) -> None: 7 | print("begin execution") 8 | 9 | @only(patterns=["a"]) 10 | def read(self, dyn_ast, iid, value): 11 | print(f"read value {value}") 12 | 13 | def end_execution(self) -> None: 14 | print("end execution") 15 | -------------------------------------------------------------------------------- /tests/filters/read_only/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | read value 10 3 | 10 4 | 20 5 | read value 10 6 | 30 7 | end execution -------------------------------------------------------------------------------- /tests/filters/read_only/program.py: -------------------------------------------------------------------------------- 1 | a = 10 2 | b = 20 3 | print(a) 4 | print(b) 5 | print(a + b) 6 | -------------------------------------------------------------------------------- /tests/filters/read_only/skip.txt: -------------------------------------------------------------------------------- 1 | TODO: Add support for filtering based on variable name -------------------------------------------------------------------------------- /tests/filters/repr/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | from dynapyt.instrument.filters import ignore 3 | 4 | 5 | class Analysis(BaseAnalysis): 6 | def begin_execution(self): 7 | print("begin execution") 8 | 9 | def end_execution(self): 10 | print("end execution") 11 | 12 | @ignore(patterns=["foo"]) 13 | def pre_call(self, dyn_ast, iid, function, pos_args, kw_args): 14 | print(f"pre call at {iid}") 15 | -------------------------------------------------------------------------------- /tests/filters/repr/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | pre call at 3 3 | pre call at 4 4 | pre call at 1 5 | pre call at 5 6 | X 7 | end execution -------------------------------------------------------------------------------- /tests/filters/repr/program.py: -------------------------------------------------------------------------------- 1 | class X: 2 | def __repr__(self): 3 | return self.__str__() 4 | 5 | def __str__(self) -> str: 6 | return "X" 7 | 8 | 9 | x = X() 10 | print(repr(x)) 11 | -------------------------------------------------------------------------------- /tests/filters/string_literal_only/analysis.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 3 | from dynapyt.instrument.filters import only 4 | 5 | 6 | class TestAnalysis(BaseAnalysis): 7 | def begin_execution(self) -> None: 8 | print("begin execution") 9 | 10 | @only(patterns=["'abc'", '"abc"', "abc"]) 11 | def string(self, dyn_ast: str, iid: int, val: Any) -> Any: 12 | print(f'string literal "{val}"') 13 | 14 | def end_execution(self) -> None: 15 | print("end execution") 16 | -------------------------------------------------------------------------------- /tests/filters/string_literal_only/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | string literal "abc" 3 | 26 4 | end execution -------------------------------------------------------------------------------- /tests/filters/string_literal_only/program.py: -------------------------------------------------------------------------------- 1 | x = "abc" 2 | y = 23 3 | z = len(x) + y 4 | a = "xyz" 5 | print(z) 6 | -------------------------------------------------------------------------------- /tests/filters/super/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | from dynapyt.instrument.filters import only 3 | 4 | 5 | class TestAnalysis(BaseAnalysis): 6 | @only(patterns=["foo"]) 7 | def post_call(self, dyn_ast: str, iid: int, result, call, pos_args, kw_args): 8 | print(f"post call of {call}") 9 | -------------------------------------------------------------------------------- /tests/filters/super/expected.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/filters/super/program.py: -------------------------------------------------------------------------------- 1 | class X: 2 | def __getattribute__(self, name: str): 3 | res = super().__getattribute__(name) 4 | return res 5 | 6 | 7 | x = X() 8 | print(x.__class__) 9 | -------------------------------------------------------------------------------- /tests/manipulate_single_hook/decorator/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | class TestAnalysis(BaseAnalysis): 4 | def begin_execution(self) -> None: 5 | print("begin execution") 6 | 7 | def enter_decorator(self, dyn_ast: str, iid: int, decorator: str, args, kwargs) -> None: 8 | print("enter decorator: ", decorator) 9 | 10 | def exit_decorator(self, dyn_ast: str, iid: int, decorator: str, result, args, kwargs) -> None: 11 | print("exit decorator: ", decorator) 12 | number = 10 13 | print("Number returned from exit_decorator: ", number) 14 | return number 15 | 16 | def end_execution(self) -> None: 17 | print("end execution") -------------------------------------------------------------------------------- /tests/manipulate_single_hook/decorator/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | enter decorator: wrapper 3 | Decorator function before 4 | Number returned from function: 2 5 | Decorator function after 6 | exit decorator: wrapper 7 | Number returned from exit_decorator: 10 8 | Number returned from decorated function: 10 9 | end execution -------------------------------------------------------------------------------- /tests/manipulate_single_hook/decorator/program.py: -------------------------------------------------------------------------------- 1 | def decorator_function_one(func): 2 | def wrapper(): 3 | print("Decorator function before") 4 | func() 5 | print("Decorator function after") 6 | return wrapper 7 | 8 | @decorator_function_one 9 | def return_number(): 10 | number = 2 11 | print("Number returned from function: ", number) 12 | return number 13 | 14 | result = return_number() 15 | print("Number returned from decorated function: ", result) 16 | 17 | 18 | -------------------------------------------------------------------------------- /tests/manipulate_single_hook/enter_control_flow/analysis.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 3 | 4 | 5 | class TestAnalysis(BaseAnalysis): 6 | def enter_control_flow( 7 | self, dyn_ast: str, iid: int, cond_value: bool 8 | ) -> Optional[bool]: 9 | print(f"branching point: {cond_value}") 10 | return not cond_value 11 | -------------------------------------------------------------------------------- /tests/manipulate_single_hook/enter_control_flow/expected.txt: -------------------------------------------------------------------------------- 1 | branching point: True 2 | false 1 3 | branching point: False 4 | loop 2 5 | branching point: False 6 | true 3 7 | branching point: True 8 | end -------------------------------------------------------------------------------- /tests/manipulate_single_hook/enter_control_flow/program.py: -------------------------------------------------------------------------------- 1 | foo = [1,2,3] 2 | if len(foo) > 2: 3 | print("true 1") 4 | else: 5 | print("false 1") 6 | while foo is None: 7 | print("loop 2") 8 | foo.append(4) 9 | if len(foo) == 7: 10 | print("true 3") 11 | foo = None 12 | print("end") -------------------------------------------------------------------------------- /tests/manipulate_single_hook/literal/analysis.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 3 | 4 | 5 | class TestAnalysis(BaseAnalysis): 6 | def literal(self, dyn_ast: str, iid: int, val: Any) -> Any: 7 | print(f"literal hook called: {val}") 8 | if val == 5: 9 | return "abc" 10 | if isinstance(val, dict): 11 | return "abc" 12 | if isinstance(val, list): 13 | return "abc" 14 | -------------------------------------------------------------------------------- /tests/manipulate_single_hook/literal/expected.txt: -------------------------------------------------------------------------------- 1 | literal hook called: 5 2 | literal hook called: 99 3 | abc 4 | literal hook called: a 5 | literal hook called: 1 6 | literal hook called: b 7 | literal hook called: 2 8 | literal hook called: {'a': 1, 'b': 2} 9 | abc 10 | literal hook called: 1 11 | literal hook called: 2 12 | literal hook called: 3 13 | literal hook called: [1, 2, 3] 14 | abc -------------------------------------------------------------------------------- /tests/manipulate_single_hook/literal/program.py: -------------------------------------------------------------------------------- 1 | class Object(object): 2 | pass 3 | 4 | 5 | obj = Object() 6 | obj.a = 5 7 | obj.b = 99 8 | x = obj.a 9 | print(x) 10 | a = {"a": 1, "b": 2} 11 | print(a) 12 | b = [1, 2, 3] 13 | print(b) 14 | -------------------------------------------------------------------------------- /tests/manipulate_single_hook/raise/analysis.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional 2 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 3 | 4 | 5 | class TestAnalysis(BaseAnalysis): 6 | def _raise(self, dyn_ast: str, iid: int, exc: Exception, cause: Any) -> Optional[Exception]: 7 | print(f"Exception with message {exc} and cause with message {cause}") 8 | if type(exc) == KeyError: 9 | return TypeError() -------------------------------------------------------------------------------- /tests/manipulate_single_hook/raise/expected.txt: -------------------------------------------------------------------------------- 1 | Exception with message 'foo' and cause with message bar 2 | TypeError caught 3 | Done -------------------------------------------------------------------------------- /tests/manipulate_single_hook/raise/program.py: -------------------------------------------------------------------------------- 1 | y = 4 2 | try: 3 | if y > 3: 4 | raise KeyError("foo") from RuntimeError("bar") 5 | except TypeError: 6 | print("TypeError caught") 7 | print("Done") -------------------------------------------------------------------------------- /tests/manipulate_single_hook/read_subscript/analysis.py: -------------------------------------------------------------------------------- 1 | from typing import Any, List, Union, Tuple 2 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 3 | 4 | 5 | class TestAnalysis(BaseAnalysis): 6 | def read_subscript( 7 | self, dyn_ast: str, iid: int, base: Any, sl: List[Union[int, Tuple]], val: Any 8 | ) -> Any: 9 | print(f"read from base {base} subscript {sl} gives value {val}") 10 | if type(val) == int: 11 | return val + 1 12 | elif type(val) == list: 13 | return [x + 1 for x in val] 14 | -------------------------------------------------------------------------------- /tests/manipulate_single_hook/read_subscript/expected.txt: -------------------------------------------------------------------------------- 1 | read from base [1, 2, 3, 4, 5] subscript [4] gives value 5 2 | x is 6 3 | read from base [1, 2, 3, 4, 5] subscript [slice(1, 3, None)] gives value [2, 3] 4 | y is [3, 4] -------------------------------------------------------------------------------- /tests/manipulate_single_hook/read_subscript/program.py: -------------------------------------------------------------------------------- 1 | arr = [1, 2, 3, 4, 5] 2 | 3 | x = arr[4] 4 | print(f"x is {x}") 5 | y = arr[1:3] 6 | print(f"y is {y}") -------------------------------------------------------------------------------- /tests/manipulate_single_hook/write/analysis.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 3 | from dynapyt.utils.AnalysisUtils import get_old_value 4 | 5 | 6 | class TestAnalysis(BaseAnalysis): 7 | def write(self, dyn_ast: str, iid: int, old_vals: Any, new_val: Any) -> Any: 8 | old = get_old_value(old_vals[0]) 9 | print(f"write with old={old} and new={new_val}") 10 | if isinstance(new_val, str): 11 | return "abc" 12 | -------------------------------------------------------------------------------- /tests/manipulate_single_hook/write/expected.txt: -------------------------------------------------------------------------------- 1 | write with old=undefined_value and new={} 2 | write with old=undefined_value and new=-999.5 3 | write with old=undefined_value and new=hi there 4 | write with old=-999.5 and new=555.9 5 | 555.9 6 | abc -------------------------------------------------------------------------------- /tests/manipulate_single_hook/write/program.py: -------------------------------------------------------------------------------- 1 | the_var = {} 2 | the_var['a'] = -999.5 3 | the_other_var = "hi there" 4 | the_var['a'] = 555.9 5 | print(the_var['a']) 6 | print(the_other_var) -------------------------------------------------------------------------------- /tests/regression/add_assign_list_str/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | 4 | class TestAnalysis(BaseAnalysis): 5 | def add_assign(self, dyn_ast, iid, left, right): 6 | print(f"add assign: {left} += {right}") 7 | -------------------------------------------------------------------------------- /tests/regression/add_assign_list_str/expected.txt: -------------------------------------------------------------------------------- 1 | add assign: at <...>> += hello -------------------------------------------------------------------------------- /tests/regression/add_assign_list_str/program.py: -------------------------------------------------------------------------------- 1 | a = [] 2 | a += "hello" 3 | -------------------------------------------------------------------------------- /tests/regression/assert_no_space/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | class TestAnalysis(BaseAnalysis): 4 | def post_call(self, dyn_ast, iid, result, call, pos_args, kw_args): 5 | print(f"Called with {result}") -------------------------------------------------------------------------------- /tests/regression/assert_no_space/expected.txt: -------------------------------------------------------------------------------- 1 | Called with 3 -------------------------------------------------------------------------------- /tests/regression/assert_no_space/program.py: -------------------------------------------------------------------------------- 1 | l = [1, 2, 3] 2 | assert(len(l)) == 3 3 | -------------------------------------------------------------------------------- /tests/regression/async_for/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | 4 | class TestAnalysis(BaseAnalysis): 5 | def enter_for(self, dyn_ast, iid, next_val, iterable): 6 | print(f"looping {next_val}") 7 | -------------------------------------------------------------------------------- /tests/regression/async_for/expected.txt: -------------------------------------------------------------------------------- 1 | looping 0 2 | 0 3 | looping 1 4 | 1 5 | looping 2 6 | 2 7 | looping -------------------------------------------------------------------------------- /tests/regression/async_for/program.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | 4 | async def get_next_async(): 5 | for i in range(3): 6 | yield i 7 | await asyncio.sleep(0.5) 8 | 9 | 10 | async def main(): 11 | async for i in get_next_async(): 12 | print(i) 13 | 14 | 15 | asyncio.run(main()) 16 | -------------------------------------------------------------------------------- /tests/regression/async_return/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | 4 | class TestAnalysis(BaseAnalysis): 5 | def begin_execution(self): 6 | print("begin execution") 7 | 8 | def function_exit(self, dyn_ast, iid, name, result): 9 | print(f"function {name} exited with result {result}") 10 | 11 | def end_execution(self): 12 | print("end execution") 13 | -------------------------------------------------------------------------------- /tests/regression/async_return/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | function foo exited with result 0 3 | 0 4 | function foo exited with result 1 5 | 1 6 | function foo exited with result 2 7 | 2 8 | function main exited with result None 9 | end execution -------------------------------------------------------------------------------- /tests/regression/async_return/program.py: -------------------------------------------------------------------------------- 1 | async def foo(): 2 | for i in range(3): 3 | yield i 4 | return 5 | 6 | 7 | async def main(): 8 | async for i in foo(): 9 | print(i) 10 | 11 | 12 | import asyncio 13 | 14 | asyncio.run(main()) 15 | -------------------------------------------------------------------------------- /tests/regression/attr_recursion/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | 4 | class TestAnalysis(BaseAnalysis): 5 | def begin_execution(self): 6 | print("begin execution") 7 | 8 | def read_attribute(self, dyn_ast, iid, base, attr, val): 9 | pass 10 | 11 | def read_identifier(self, dyn_ast, iid, val): 12 | pass 13 | 14 | def post_call(self, dyn_ast, iid, result, function, pos_args, kw_args): 15 | if result is function: 16 | return 17 | _self = function 18 | if isinstance(_self, str): 19 | pass 20 | print(f"Function call") 21 | 22 | def end_execution(self): 23 | print("end execution") 24 | -------------------------------------------------------------------------------- /tests/regression/attr_recursion/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | Function call 3 | Function call 4 | Function call 5 | Function call 6 | Function call 7 | Function call 8 | Function call 9 | Function call 10 | Function call 11 | 12 | Function call 13 | end execution -------------------------------------------------------------------------------- /tests/regression/attr_recursion/program.py: -------------------------------------------------------------------------------- 1 | import types 2 | 3 | 4 | class EnhancedModule(types.ModuleType): 5 | def __bool__(self): 6 | return vars(self).get("__bool__", lambda: True)() 7 | 8 | def __getattribute__(self, attr): 9 | try: 10 | ret = super().__getattribute__(attr) 11 | except AttributeError: 12 | if attr.startswith("__") and attr.endswith("__"): 13 | raise 14 | getter = getattr(self, "__getattr__", None) 15 | if not getter: 16 | raise 17 | ret = getter(attr) 18 | return ret.fget() if isinstance(ret, property) else ret 19 | 20 | 21 | x = EnhancedModule("x") 22 | print(x) 23 | -------------------------------------------------------------------------------- /tests/regression/deep_site_sensitive_calls/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | 4 | class TestAnalysis(BaseAnalysis): 5 | def begin_execution(self) -> None: 6 | print("begin execution") 7 | 8 | def pre_call(self, dyn_ast: str, iid: int, function, pos_args, kw_args) -> None: 9 | print(f"pre call of {function.__name__}") 10 | 11 | def binary_operation(self, dyn_ast, iid, op, left, right, result): 12 | print(f"binary operation {left} {op} {right} = {result}") 13 | 14 | def end_execution(self) -> None: 15 | print("end execution") 16 | -------------------------------------------------------------------------------- /tests/regression/deep_site_sensitive_calls/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | pre call of B 3 | pre call of __init__ 4 | pre call of f 5 | pre call of f 6 | binary operation 1 Add 2 = 3 7 | pre call of len 8 | pre call of len 9 | binary operation 14 Add 14 = 28 10 | end execution -------------------------------------------------------------------------------- /tests/regression/deep_site_sensitive_calls/program.py: -------------------------------------------------------------------------------- 1 | class A: 2 | def __init__(self): 3 | self.a = 1 4 | 5 | def f(self): 6 | return self.a 7 | 8 | 9 | class B(A): 10 | def __init__(self): 11 | super().__init__() 12 | self.b = 2 13 | 14 | def f(self): 15 | return super().f() + self.b 16 | 17 | 18 | b = B() 19 | b.f() 20 | 21 | c = len(locals()) + len(globals()) 22 | -------------------------------------------------------------------------------- /tests/regression/delete_track_read/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | 4 | class TestAnalysis(BaseAnalysis): 5 | def begin_execution(self) -> None: 6 | print("begin execution") 7 | 8 | def read(self, dyn_ast, iid, value): 9 | print(f"read value {value}") 10 | 11 | def end_execution(self) -> None: 12 | print("end execution") 13 | -------------------------------------------------------------------------------- /tests/regression/delete_track_read/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | read value 10 3 | 10 4 | end execution -------------------------------------------------------------------------------- /tests/regression/delete_track_read/program.py: -------------------------------------------------------------------------------- 1 | a = 10 2 | print(a) 3 | del a 4 | -------------------------------------------------------------------------------- /tests/regression/file_entry/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | from typing import Callable, Dict, Tuple 3 | 4 | 5 | class TestAnalysis(BaseAnalysis): 6 | def pre_call( 7 | self, dyn_ast: str, iid: int, function: Callable, pos_args: Tuple, kw_args: Dict 8 | ) -> None: 9 | print(f"pre call of {function.__name__}") 10 | -------------------------------------------------------------------------------- /tests/regression/file_entry/expected.txt: -------------------------------------------------------------------------------- 1 | pre call of print 2 | <...>program.py -------------------------------------------------------------------------------- /tests/regression/file_entry/program.py: -------------------------------------------------------------------------------- 1 | # DYNAPYT: Run as file 2 | print(__file__) 3 | -------------------------------------------------------------------------------- /tests/regression/filters_getattr/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | from dynapyt.instrument.filters import only 3 | 4 | 5 | class TestAnalysis(BaseAnalysis): 6 | @only(patterns=["foo"]) 7 | def pre_call(self, dyn_ast: str, iid: int, function, pos_args, kw_args) -> None: 8 | print(f"pree call of {function.__name__}") 9 | -------------------------------------------------------------------------------- /tests/regression/filters_getattr/expected.txt: -------------------------------------------------------------------------------- 1 | OK 2 | # Exception: Some error text -------------------------------------------------------------------------------- /tests/regression/filters_getattr/program.py: -------------------------------------------------------------------------------- 1 | class DeferredError: 2 | def __init__(self, ex: BaseException): 3 | self.ex = ex 4 | 5 | def __getattr__(self, elt: str) -> None: 6 | raise self.ex 7 | 8 | @staticmethod 9 | def new(ex: BaseException): 10 | """ 11 | Creates an object that raises the wrapped exception ``ex`` when used. 12 | """ 13 | return DeferredError(ex) 14 | 15 | 16 | def deferred_error() -> None: 17 | thing = DeferredError.new(ValueError("Some error text")) 18 | print("OK") 19 | x = thing.some_attr 20 | print("Not OK") 21 | 22 | 23 | deferred_error() 24 | -------------------------------------------------------------------------------- /tests/regression/function_exit/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | from typing import Any 3 | 4 | 5 | class TestAnalysis(BaseAnalysis): 6 | def begin_execution(self) -> None: 7 | print(f"begin execution") 8 | 9 | def function_exit( 10 | self, dyn_ast: str, function_iid: int, name: str, result: Any 11 | ) -> Any: 12 | print(f"Exiting function {name} with result {result}") 13 | 14 | def end_execution(self) -> None: 15 | print(f"end execution") 16 | -------------------------------------------------------------------------------- /tests/regression/function_exit/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | Exiting function foo with result 1 3 | Exiting function bar with result 1 4 | Exiting function bar with result None 5 | end execution -------------------------------------------------------------------------------- /tests/regression/function_exit/program.py: -------------------------------------------------------------------------------- 1 | def foo(): 2 | x = 1 3 | return x 4 | 5 | 6 | def bar(x): 7 | if x < 5: 8 | return x 9 | 10 | 11 | a = foo() 12 | b = bar(1) 13 | b = bar(10) 14 | -------------------------------------------------------------------------------- /tests/regression/gen_expr_pos_arg/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | 4 | class TestAnalysis(BaseAnalysis): 5 | def begin_execution(self): 6 | print("begin execution") 7 | 8 | def end_execution(self): 9 | print("end execution") 10 | 11 | def pre_call(self, dyn_ast, iid, call, pos_args, kw_args): 12 | print( 13 | f"pre call of {call.__name__} with pos args {pos_args} and kw args {kw_args}" 14 | ) 15 | -------------------------------------------------------------------------------- /tests/regression/gen_expr_pos_arg/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | pre call of join with pos args [ at <...>] and kw args {} 3 | pre call of print with pos args ['h.e.l.l.o'] and kw args {} 4 | h.e.l.l.o 5 | end execution -------------------------------------------------------------------------------- /tests/regression/gen_expr_pos_arg/program.py: -------------------------------------------------------------------------------- 1 | s = ".".join(x for x in "hello") 2 | print(s) 3 | -------------------------------------------------------------------------------- /tests/regression/inline_break_continue/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | 4 | class TestAnalysis(BaseAnalysis): 5 | def _break(self, dyn_ast, iid, loop_iid): 6 | print(f"breaking at {iid} of {loop_iid}") 7 | 8 | def _continue(self, dyn_ast, iid, loop_iid): 9 | print(f"continuing at {iid} of {loop_iid}") 10 | -------------------------------------------------------------------------------- /tests/regression/inline_break_continue/expected.txt: -------------------------------------------------------------------------------- 1 | continuing at 1 of 0 2 | 1 3 | continuing at 1 of 0 4 | 3 5 | continuing at 1 of 0 6 | 5 7 | breaking at 2 of 0 -------------------------------------------------------------------------------- /tests/regression/inline_break_continue/program.py: -------------------------------------------------------------------------------- 1 | for i in range(10): 2 | if i % 2 == 0: continue 3 | print(i) 4 | if i == 5: break -------------------------------------------------------------------------------- /tests/regression/lhs_read_sub/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | 4 | class TestAnalysis(BaseAnalysis): 5 | def read_subscript(self, dyn_ast, iid, base, sl, value): 6 | print(f"read {value}") 7 | -------------------------------------------------------------------------------- /tests/regression/lhs_read_sub/expected.txt: -------------------------------------------------------------------------------- 1 | read 1 2 | read 0 -------------------------------------------------------------------------------- /tests/regression/lhs_read_sub/program.py: -------------------------------------------------------------------------------- 1 | signs = [1, 0] 2 | dur = [1, 2] 3 | 4 | dur[0] *= -1 if signs[0] else 1 5 | dur[1] *= -1 if signs[1] else 1 6 | -------------------------------------------------------------------------------- /tests/regression/list_comp_if/analysis.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Iterable 2 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 3 | 4 | 5 | class TestAnalysis(BaseAnalysis): 6 | def begin_execution(self) -> None: 7 | print("begin execution") 8 | 9 | def enter_control_flow( 10 | self, dyn_ast: str, iid: int, cond_value: bool 11 | ) -> Optional[bool]: 12 | print(f"enter control flow event with condition {cond_value}") 13 | 14 | def enter_for( 15 | self, dyn_ast: str, iid: int, next_value: bool, iterable: Iterable 16 | ) -> Optional[bool]: 17 | print(f"for condition evaluates to {next_value}") 18 | 19 | def exit_for(self, dyn_ast: str, iid: int): 20 | print(f"done with for statement") 21 | 22 | def exit_control_flow(self, dyn_ast: str, iid: int) -> None: 23 | print(f"done with control flow statement") 24 | 25 | def end_execution(self) -> None: 26 | print("end execution") 27 | -------------------------------------------------------------------------------- /tests/regression/list_comp_if/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | enter control flow event with condition True 3 | for condition evaluates to 0 4 | enter control flow event with condition True 5 | done with control flow statement 6 | enter control flow event with condition True 7 | for condition evaluates to 1 8 | enter control flow event with condition True 9 | done with control flow statement 10 | enter control flow event with condition True 11 | for condition evaluates to 2 12 | enter control flow event with condition True 13 | done with control flow statement 14 | enter control flow event with condition True 15 | for condition evaluates to 3 16 | enter control flow event with condition True 17 | done with control flow statement 18 | enter control flow event with condition True 19 | for condition evaluates to 4 20 | enter control flow event with condition True 21 | done with control flow statement 22 | enter control flow event with condition True 23 | for condition evaluates to 5 24 | enter control flow event with condition True 25 | done with control flow statement 26 | enter control flow event with condition True 27 | for condition evaluates to 6 28 | enter control flow event with condition True 29 | done with control flow statement 30 | enter control flow event with condition True 31 | for condition evaluates to 7 32 | enter control flow event with condition True 33 | done with control flow statement 34 | enter control flow event with condition True 35 | for condition evaluates to 8 36 | enter control flow event with condition True 37 | done with control flow statement 38 | enter control flow event with condition True 39 | for condition evaluates to 9 40 | enter control flow event with condition True 41 | done with control flow statement 42 | enter control flow event with condition False 43 | for condition evaluates to 44 | done with control flow statement 45 | done with for statement 46 | end execution -------------------------------------------------------------------------------- /tests/regression/list_comp_if/program.py: -------------------------------------------------------------------------------- 1 | [0 if True else v for v in range(10)] 2 | -------------------------------------------------------------------------------- /tests/regression/no_analysis_exception/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | 4 | class TestAnalysis(BaseAnalysis): 5 | pass 6 | -------------------------------------------------------------------------------- /tests/regression/no_analysis_exception/expected.txt: -------------------------------------------------------------------------------- 1 | # Exception: No analysis -------------------------------------------------------------------------------- /tests/regression/no_analysis_exception/program.py: -------------------------------------------------------------------------------- 1 | raise Exception("No analysis") 2 | -------------------------------------------------------------------------------- /tests/regression/yield_from/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | 4 | class TestAnalysis(BaseAnalysis): 5 | def begin_execution(self): 6 | print("begin execution") 7 | 8 | def end_execution(self): 9 | print("end execution") 10 | 11 | def write(self, dyn_ast, iid, left, right): 12 | print(f"Writing {right}") 13 | -------------------------------------------------------------------------------- /tests/regression/yield_from/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | Writing range(0, 3) 3 | Writing 0 4 | 0 5 | Writing range(0, 3) 6 | Writing 0 7 | 0 8 | Writing range(0, 3) 9 | Writing 0 10 | 0 11 | end execution -------------------------------------------------------------------------------- /tests/regression/yield_from/program.py: -------------------------------------------------------------------------------- 1 | def foo(): 2 | x = yield from range(3) 3 | 4 | 5 | for i in range(3): 6 | res = foo().__next__() 7 | print(res) 8 | -------------------------------------------------------------------------------- /tests/regression/zip/analysis.py: -------------------------------------------------------------------------------- 1 | import collections 2 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 3 | 4 | 5 | class TestAnalysis(BaseAnalysis): 6 | def begin_execution(self): 7 | print("begin execution") 8 | 9 | def end_execution(self): 10 | print("end execution") 11 | 12 | def post_call(self, dyn_ast, iid, result, call, pos_args, kw_args): 13 | print(f"post call of {call.__name__}") 14 | 15 | def pre_call(self, dyn_ast, iid, function, pos_args, kw_args): 16 | if isinstance(pos_args[0], collections.abc.Iterator): 17 | x = list(pos_args[0]) 18 | pos_args[0] = x 19 | -------------------------------------------------------------------------------- /tests/regression/zip/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | post call of zip 3 | post call of list 4 | [(1, 4), (2, 5), (3, 6)] 5 | post call of print 6 | post call of zip 7 | post call of zip 8 | (1, 2, 3) 9 | post call of print 10 | (4, 5, 6) 11 | post call of print 12 | end execution -------------------------------------------------------------------------------- /tests/regression/zip/program.py: -------------------------------------------------------------------------------- 1 | a = [1, 2, 3] 2 | b = [4, 5, 6] 3 | print(list(zip(a, b))) 4 | c, d = zip(*zip(a, b)) 5 | print(c) 6 | print(d) 7 | -------------------------------------------------------------------------------- /tests/test_analysis/call_graph/expected.txt: -------------------------------------------------------------------------------- 1 | foo 2 | bar 3 | { 4 | "test_analysis.call_graph.program": [ 5 | "test_analysis.call_graph.program.foo" 6 | ], 7 | "test_analysis.call_graph.program.foo": [ 8 | "builtins.print", 9 | "test_analysis.call_graph.program.bar" 10 | ], 11 | "test_analysis.call_graph.program.bar": [ 12 | "builtins.print" 13 | ] 14 | } -------------------------------------------------------------------------------- /tests/test_analysis/call_graph/program.py: -------------------------------------------------------------------------------- 1 | def foo(): 2 | print("foo") 3 | bar() 4 | 5 | 6 | def bar(): 7 | print("bar") 8 | 9 | 10 | foo() 11 | -------------------------------------------------------------------------------- /tests/test_analysis/multiple_analyses/analysis.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Dict, Tuple, Any 2 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 3 | 4 | 5 | class TestCallAnalysis(BaseAnalysis): 6 | def begin_execution(self) -> None: 7 | print("begin execution of call analysis") 8 | 9 | def pre_call( 10 | self, dyn_ast: str, iid: int, function: Callable, pos_args: Tuple, kw_args: Dict 11 | ) -> None: 12 | print(f"pre call of {function.__name__}") 13 | 14 | def post_call( 15 | self, 16 | dyn_ast: str, 17 | iid: int, 18 | result: Any, 19 | call: Callable, 20 | pos_args: Tuple, 21 | kw_args: Dict, 22 | ) -> Any: 23 | print(f"post call of {call.__name__}") 24 | 25 | def end_execution(self) -> None: 26 | print("end execution of call analysis") 27 | 28 | 29 | class TestIntAnalysis(BaseAnalysis): 30 | def begin_execution(self) -> None: 31 | print("begin execution of int analysis") 32 | 33 | def integer(self, dyn_ast: str, iid: int, val: Any) -> Any: 34 | print(f"integer literal {val}") 35 | 36 | def end_execution(self) -> None: 37 | print("end execution of int analysis") 38 | 39 | 40 | class TestAnalysis(BaseAnalysis): 41 | def multiply( 42 | self, dyn_ast: str, iid: int, left: Any, right: Any, result: Any 43 | ) -> Any: 44 | print(f"multiplying {left} and {right} gives {result}") 45 | -------------------------------------------------------------------------------- /tests/test_analysis/multiple_analyses/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution of call analysis 2 | begin execution of int analysis 3 | integer literal 10 4 | integer literal 20 5 | multiplying 10 and 20 gives 200 6 | pre call of print 7 | 200 8 | post call of print 9 | end execution of call analysis 10 | end execution of int analysis -------------------------------------------------------------------------------- /tests/test_analysis/multiple_analyses/program.py: -------------------------------------------------------------------------------- 1 | a = 10 2 | b = 20 3 | c = a * b 4 | print(c) 5 | -------------------------------------------------------------------------------- /tests/test_coverage/multi_file/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | 4 | class TestAnalysis(BaseAnalysis): 5 | def begin_execution(self): 6 | print("begin execution") 7 | 8 | def end_execution(self): 9 | print("end execution") 10 | 11 | def pre_call(self, dyn_ast, iid, call, pos_args, kw_args): 12 | print(f"pre call {call.__name__}") 13 | -------------------------------------------------------------------------------- /tests/test_coverage/multi_file/exp_coverage.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_coverage/multi_file/program.py.orig": {"8": {"TestAnalysis": 2}, "5": {"TestAnalysis": 1}}, 3 | "test_coverage/multi_file/program2.py.orig": {"6": {"TestAnalysis": 1}} 4 | } -------------------------------------------------------------------------------- /tests/test_coverage/multi_file/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | pre call baz 3 | pre call bar 4 | pre call foo 5 | pre call print 6 | 216 7 | end execution -------------------------------------------------------------------------------- /tests/test_coverage/multi_file/program.py: -------------------------------------------------------------------------------- 1 | from .program2 import bar 2 | 3 | 4 | def baz(x): 5 | return bar(x) ** 3 6 | 7 | 8 | print(baz(2)) 9 | -------------------------------------------------------------------------------- /tests/test_coverage/multi_file/program2.py: -------------------------------------------------------------------------------- 1 | def foo(x): 2 | return x + 1 3 | 4 | 5 | def bar(x): 6 | return foo(x) * 2 7 | -------------------------------------------------------------------------------- /tests/test_coverage/runtime_event/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | 4 | class TestAnalysis(BaseAnalysis): 5 | def begin_execution(self): 6 | print("begin_execution") 7 | 8 | def end_execution(self): 9 | print("end_execution") 10 | 11 | def runtime_event(self, dyn_ast, iid): 12 | print(f"runtime_event @ {iid}") 13 | -------------------------------------------------------------------------------- /tests/test_coverage/runtime_event/exp_coverage.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_coverage/runtime_event/program.py.orig": {"1": {"TestAnalysis": 2}, "2": {"TestAnalysis": 13}, "3": {"TestAnalysis": 8}, "4": {"TestAnalysis": 4}} 3 | } 4 | -------------------------------------------------------------------------------- /tests/test_coverage/runtime_event/expected.txt: -------------------------------------------------------------------------------- 1 | begin_execution 2 | runtime_event @ 0 3 | runtime_event @ 1 4 | runtime_event @ 3 5 | runtime_event @ 4 6 | runtime_event @ 5 7 | runtime_event @ 2 8 | runtime_event @ 8 9 | runtime_event @ 6 10 | runtime_event @ 7 11 | runtime_event @ 9 12 | runtime_event @ 10 13 | runtime_event @ 11 14 | 2 15 | runtime_event @ 3 16 | runtime_event @ 4 17 | runtime_event @ 5 18 | runtime_event @ 2 19 | runtime_event @ 8 20 | runtime_event @ 6 21 | runtime_event @ 7 22 | runtime_event @ 9 23 | runtime_event @ 10 24 | runtime_event @ 11 25 | 3 26 | runtime_event @ 3 27 | runtime_event @ 4 28 | runtime_event @ 5 29 | runtime_event @ 2 30 | runtime_event @ 2 31 | end_execution -------------------------------------------------------------------------------- /tests/test_coverage/runtime_event/program.py: -------------------------------------------------------------------------------- 1 | a = 1 2 | while a < 3: 3 | a = a + 1 4 | print(a) 5 | -------------------------------------------------------------------------------- /tests/test_coverage/single_hook/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | 4 | class TestAnalysis(BaseAnalysis): 5 | def begin_execution(self): 6 | print("begin_execution") 7 | 8 | def end_execution(self): 9 | print("end_execution") 10 | 11 | def add(self, dyn_ast, iid, left, right, result): 12 | print(f"add: {left} + {right} = {result}") 13 | -------------------------------------------------------------------------------- /tests/test_coverage/single_hook/exp_coverage.json: -------------------------------------------------------------------------------- 1 | { 2 | "test_coverage/single_hook/program.py.orig": {"3": {"TestAnalysis": 2}} 3 | } 4 | -------------------------------------------------------------------------------- /tests/test_coverage/single_hook/expected.txt: -------------------------------------------------------------------------------- 1 | begin_execution 2 | add: 1 + 1 = 2 3 | 2 4 | add: 2 + 1 = 3 5 | 3 6 | end_execution -------------------------------------------------------------------------------- /tests/test_coverage/single_hook/program.py: -------------------------------------------------------------------------------- 1 | a = 1 2 | while a < 3: 3 | a = a + 1 4 | print(a) 5 | -------------------------------------------------------------------------------- /tests/trace_single_hook/add_assign/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | 4 | class TestAnalysis(BaseAnalysis): 5 | def begin_execution(self): 6 | print("begin execution") 7 | 8 | def end_execution(self): 9 | print("end execution") 10 | 11 | def add_assign(self, dyn_ast, iid, left, right): 12 | print(f"add_assign: {left()} += {right}") 13 | -------------------------------------------------------------------------------- /tests/trace_single_hook/add_assign/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | add_assign: 1 += 2 3 | 3 4 | add_assign: test += test 5 | testtest 6 | add_assign: 1 += 1 7 | 2 8 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/add_assign/program.py: -------------------------------------------------------------------------------- 1 | class X: 2 | a = 1 3 | 4 | 5 | a = 1 6 | a += 2 7 | print(a) 8 | s = "test" 9 | s += "test" 10 | print(s) 11 | x = X() 12 | x.a += 1 13 | print(x.a) 14 | -------------------------------------------------------------------------------- /tests/trace_single_hook/annotated_assignment/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | 4 | class TestAnalysis(BaseAnalysis): 5 | def begin_execution(self) -> None: 6 | print("begin execution") 7 | 8 | def read(self, dyn_ast, iid, value): 9 | print(f"read value {value}") 10 | 11 | def write(self, dyn_ast, iid, old_values, new_value): 12 | print(f"Writing {new_value} to {[ov.__code__.co_names for ov in old_values]}") 13 | 14 | def end_execution(self) -> None: 15 | print("end execution") 16 | -------------------------------------------------------------------------------- /tests/trace_single_hook/annotated_assignment/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | Writing 10 to [('a',)] 3 | read value 10 4 | Writing 10 to [('b',)] 5 | read value 6 | Writing 10 to [('x',)] 7 | Writing > to [('c',)] 8 | Writing hello to [('c', 'y')] 9 | Writing None to [('c', 'z')] 10 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/annotated_assignment/program.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | 4 | class X: 5 | def __init__(self): 6 | self.x: int = 10 7 | 8 | 9 | a = 10 10 | b: int = a 11 | c = X() 12 | c.y: str = "hello" 13 | c.z: Optional[str] = None 14 | -------------------------------------------------------------------------------- /tests/trace_single_hook/call/analysis.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Dict, Tuple, Any 2 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 3 | from dynapyt.instrument.IIDs import IIDs 4 | 5 | 6 | class TestAnalysis(BaseAnalysis): 7 | def begin_execution(self) -> None: 8 | print("begin execution") 9 | 10 | def pre_call( 11 | self, dyn_ast: str, iid: int, function: Callable, pos_args: Tuple, kw_args: Dict 12 | ) -> None: 13 | print(f"pre call of {function.__name__}") 14 | 15 | def post_call( 16 | self, 17 | dyn_ast: str, 18 | iid: int, 19 | result: Any, 20 | call: Callable, 21 | pos_args: Tuple, 22 | kw_args: Dict, 23 | ) -> Any: 24 | print(f"post call of {call.__name__}") 25 | 26 | def end_execution(self) -> None: 27 | print("end execution") 28 | -------------------------------------------------------------------------------- /tests/trace_single_hook/call/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | pre call of foo 3 | post call of foo 4 | pre call of print 5 | hello world 6 | post call of print 7 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/call/program.py: -------------------------------------------------------------------------------- 1 | def foo(): 2 | return print 3 | 4 | 5 | foo()("hello world") 6 | -------------------------------------------------------------------------------- /tests/trace_single_hook/call_builtin/analysis.py: -------------------------------------------------------------------------------- 1 | from typing import Callable, Dict, Tuple, Any 2 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 3 | from dynapyt.instrument.IIDs import IIDs 4 | 5 | 6 | class TestAnalysis(BaseAnalysis): 7 | def begin_execution(self) -> None: 8 | print("begin execution") 9 | 10 | def pre_call( 11 | self, dyn_ast: str, iid: int, function: Callable, pos_args: Tuple, kw_args: Dict 12 | ) -> None: 13 | print(f"pre call of {function.__name__}") 14 | 15 | def post_call( 16 | self, 17 | dyn_ast: str, 18 | iid: int, 19 | result: Any, 20 | call: Callable, 21 | pos_args: Tuple, 22 | kw_args: Dict, 23 | ) -> Any: 24 | print(f"post call of {call.__name__}") 25 | 26 | def end_execution(self) -> None: 27 | print("end execution") 28 | -------------------------------------------------------------------------------- /tests/trace_single_hook/call_builtin/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | pre call of Foo 3 | post call of Foo 4 | pre call of type 5 | post call of type 6 | pre call of Foo 7 | post call of Foo 8 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/call_builtin/program.py: -------------------------------------------------------------------------------- 1 | class Foo: 2 | def __init__(self, i): 3 | self.i = i 4 | 5 | 6 | foo1 = Foo(1) 7 | foo2 = type(foo1)(2) 8 | -------------------------------------------------------------------------------- /tests/trace_single_hook/class_attr/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | 4 | class TestAnalysis(BaseAnalysis): 5 | def begin_execution(self): 6 | print("begin execution") 7 | 8 | def end_execution(self): 9 | print("end execution") 10 | 11 | def write(self, dyn_ast, iid, old_values, new_value): 12 | print(f"Writing {new_value} to {[ov.__code__.co_names for ov in old_values]}") 13 | -------------------------------------------------------------------------------- /tests/trace_single_hook/class_attr/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | Writing test to [('foo',)] 3 | test 4 | Writing > to [('x',)] 5 | test 6 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/class_attr/program.py: -------------------------------------------------------------------------------- 1 | class X: 2 | foo: str = "test" 3 | 4 | 5 | print(X.foo) 6 | x = X() 7 | print(x.foo) 8 | -------------------------------------------------------------------------------- /tests/trace_single_hook/comparison/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | 4 | class TestAnalysis(BaseAnalysis): 5 | def begin_execution(self): 6 | print("begin execution") 7 | 8 | def end_execution(self): 9 | print("end execution") 10 | 11 | def comparison(self, dyn_ast, iid, left, op, right, result): 12 | print(f"Comparing {left} {op} {right} = {result}") 13 | -------------------------------------------------------------------------------- /tests/trace_single_hook/comparison/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | Comparing 10 Is 10 = True 3 | Comparing 10 In [1, 10, 100] = True 4 | Comparing 10 NotIn [1, 2, 3] = True 5 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/comparison/program.py: -------------------------------------------------------------------------------- 1 | a = 10 2 | b = 10 3 | a is b 4 | c = [1, 10, 100] 5 | a in c 6 | d = [1, 2, 3] 7 | b not in d 8 | -------------------------------------------------------------------------------- /tests/trace_single_hook/decorators/builtin_decorator/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | class TestAnalysis(BaseAnalysis): 4 | def begin_execution(self) -> None: 5 | print("begin execution") 6 | 7 | def enter_decorator(self, dyn_ast: str, iid: int, decorator: str, args, kwargs) -> None: 8 | print("enter decorator: ", decorator) 9 | 10 | def exit_decorator(self, dyn_ast: str, iid: int, decorator: str, result, args, kwargs) -> None: 11 | print("exit decorator: ", decorator) 12 | 13 | def end_execution(self) -> None: 14 | print("end execution") -------------------------------------------------------------------------------- /tests/trace_single_hook/decorators/builtin_decorator/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | enter decorator: my_context_manager 3 | exit decorator: my_context_manager 4 | Entering context 5 | Inside context 6 | Exiting context 7 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/decorators/builtin_decorator/program.py: -------------------------------------------------------------------------------- 1 | from contextlib import contextmanager 2 | 3 | @contextmanager 4 | def my_context_manager(): 5 | print('Entering context') 6 | yield 7 | print('Exiting context') 8 | 9 | with my_context_manager(): 10 | print('Inside context') 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /tests/trace_single_hook/decorators/decorator_class_attribute/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | class TestAnalysis(BaseAnalysis): 4 | def begin_execution(self) -> None: 5 | print("begin execution") 6 | 7 | def enter_decorator(self, dyn_ast: str, iid: int, decorator: str, args, kwargs) -> None: 8 | print("enter decorator: ", decorator) 9 | 10 | def exit_decorator(self, dyn_ast: str, iid: int, decorator: str, result, args, kwargs) -> None: 11 | print("exit decorator: ", decorator) 12 | 13 | def end_execution(self) -> None: 14 | print("end execution") -------------------------------------------------------------------------------- /tests/trace_single_hook/decorators/decorator_class_attribute/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | enter decorator: wrapper 3 | Decorator function before 4 | Simple function 5 | Decorator function after 6 | exit decorator: wrapper 7 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/decorators/decorator_class_attribute/program.py: -------------------------------------------------------------------------------- 1 | class foo: 2 | def decorator_func(self, func): 3 | def wrapper(): 4 | print("Decorator function before") 5 | func() 6 | print("Decorator function after") 7 | return wrapper 8 | 9 | f = foo() 10 | 11 | @f.decorator_func 12 | def simple_function(): 13 | print("Simple function") 14 | 15 | simple_function() -------------------------------------------------------------------------------- /tests/trace_single_hook/decorators/decorator_func_with_args/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | class TestAnalysis(BaseAnalysis): 4 | def begin_execution(self) -> None: 5 | print("begin execution") 6 | 7 | def enter_decorator(self, dyn_ast: str, iid: int, decorator: str, args, kwargs) -> None: 8 | print("enter decorator: ", decorator) 9 | print("args: ", args) 10 | 11 | def exit_decorator(self, dyn_ast: str, iid: int, decorator: str, result, args, kwargs) -> None: 12 | print("exit decorator: ", decorator) 13 | 14 | def end_execution(self) -> None: 15 | print("end execution") -------------------------------------------------------------------------------- /tests/trace_single_hook/decorators/decorator_func_with_args/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | enter decorator: wrapper 3 | args: ('foo', 'bar') 4 | Decorator function before 5 | Simple function 6 | parameter 1: foo 7 | parameter 2: bar 8 | Decorator function after 9 | exit decorator: wrapper 10 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/decorators/decorator_func_with_args/program.py: -------------------------------------------------------------------------------- 1 | def decorator_function_one(func): 2 | def wrapper(*args): 3 | print("Decorator function before") 4 | func(*args) 5 | print("Decorator function after") 6 | return wrapper 7 | 8 | @decorator_function_one 9 | def simple_function(arg1, arg2): 10 | print("Simple function") 11 | print("parameter 1: ", arg1) 12 | print("parameter 2: ", arg2) 13 | 14 | simple_function("foo", "bar") 15 | 16 | -------------------------------------------------------------------------------- /tests/trace_single_hook/decorators/decorator_runtime_controlflow/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | 4 | class TestAnalysis(BaseAnalysis): 5 | def begin_execution(self) -> None: 6 | print("begin execution") 7 | 8 | def enter_decorator(self, dyn_ast: str, iid: int, decorator: str, args, kwargs) -> None: 9 | print("enter decorator: ", decorator) 10 | 11 | def exit_decorator(self, dyn_ast: str, iid: int, decorator: str, result, args, kwargs) -> None: 12 | print("exit decorator: ", decorator) 13 | 14 | def runtime_event(self, dyn_ast: str, iid: int) -> None: 15 | print("runtime event") 16 | 17 | def control_flow_event(self, dyn_ast: str, iid: int) -> None: 18 | print("control flow event") 19 | 20 | def end_execution(self) -> None: 21 | print("end execution") -------------------------------------------------------------------------------- /tests/trace_single_hook/decorators/decorator_runtime_controlflow/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | runtime event 3 | control flow event 4 | runtime event 5 | runtime event 6 | control flow event 7 | runtime event 8 | runtime event 9 | control flow event 10 | runtime event 11 | control flow event 12 | enter decorator: wrapper 13 | runtime event 14 | control flow event 15 | runtime event 16 | runtime event 17 | control flow event 18 | Decorator function before 19 | runtime event 20 | runtime event 21 | control flow event 22 | runtime event 23 | control flow event 24 | runtime event 25 | runtime event 26 | control flow event 27 | Simple function 28 | runtime event 29 | control flow event 30 | runtime event 31 | runtime event 32 | control flow event 33 | Decorator function after 34 | runtime event 35 | control flow event 36 | runtime event 37 | control flow event 38 | exit decorator: wrapper 39 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/decorators/decorator_runtime_controlflow/program.py: -------------------------------------------------------------------------------- 1 | def decorator_function_one(func): 2 | def wrapper(): 3 | print("Decorator function before") 4 | func() 5 | print("Decorator function after") 6 | return wrapper 7 | 8 | @decorator_function_one 9 | def simple_function(): 10 | print("Simple function") 11 | 12 | simple_function() 13 | 14 | -------------------------------------------------------------------------------- /tests/trace_single_hook/decorators/decorator_simple/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | class TestAnalysis(BaseAnalysis): 4 | def begin_execution(self) -> None: 5 | print("begin execution") 6 | 7 | def enter_decorator(self, dyn_ast: str, iid: int, decorator: str, args, kwargs) -> None: 8 | print("enter decorator: ", decorator) 9 | 10 | def exit_decorator(self, dyn_ast: str, iid: int, decorator: str, result, args, kwargs) -> None: 11 | print("exit decorator: ", decorator) 12 | 13 | def end_execution(self) -> None: 14 | print("end execution") -------------------------------------------------------------------------------- /tests/trace_single_hook/decorators/decorator_simple/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | enter decorator: wrapper 3 | Decorator function before 4 | Simple function 5 | Decorator function after 6 | exit decorator: wrapper 7 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/decorators/decorator_simple/program.py: -------------------------------------------------------------------------------- 1 | def decorator_function_one(func): 2 | def wrapper(): 3 | print("Decorator function before") 4 | func() 5 | print("Decorator function after") 6 | return wrapper 7 | 8 | @decorator_function_one 9 | def simple_function(): 10 | print("Simple function") 11 | 12 | simple_function() 13 | 14 | -------------------------------------------------------------------------------- /tests/trace_single_hook/decorators/decorator_with_args/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | class TestAnalysis(BaseAnalysis): 4 | def begin_execution(self) -> None: 5 | print("begin execution") 6 | 7 | def enter_decorator(self, dyn_ast: str, iid: int, decorator: str, *args, **kwargs) -> None: 8 | print("enter decorator: ", decorator) 9 | 10 | def exit_decorator(self, dyn_ast: str, iid: int, decorator: str, result, *args, **kwargs) -> None: 11 | print("exit decorator: ", decorator) 12 | 13 | def end_execution(self) -> None: 14 | print("end execution") -------------------------------------------------------------------------------- /tests/trace_single_hook/decorators/decorator_with_args/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | enter decorator: wrapper 3 | Decorator function before 4 | Simple function 5 | Decorator function after 6 | exit decorator: wrapper 7 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/decorators/decorator_with_args/program.py: -------------------------------------------------------------------------------- 1 | def decorator_function_with_args(arg1, arg2): 2 | def decorator_function(func): 3 | def wrapper(): 4 | print("Decorator function before") 5 | func() 6 | print("Decorator function after") 7 | return wrapper 8 | return decorator_function 9 | 10 | @decorator_function_with_args("arg1", "arg2") 11 | def simple_function(): 12 | print("Simple function") 13 | 14 | simple_function() 15 | 16 | -------------------------------------------------------------------------------- /tests/trace_single_hook/decorators/nested_decorator/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | class TestAnalysis(BaseAnalysis): 4 | def begin_execution(self) -> None: 5 | print("begin execution") 6 | 7 | def enter_decorator(self, dyn_ast: str, iid: int, decorator: str, args, kwargs) -> None: 8 | print("enter decorator: ", decorator) 9 | 10 | def exit_decorator(self, dyn_ast: str, iid: int, decorator: str, result, args, kwargs) -> None: 11 | print("exit decorator: ", decorator) 12 | 13 | def end_execution(self) -> None: 14 | print("end execution") -------------------------------------------------------------------------------- /tests/trace_single_hook/decorators/nested_decorator/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | enter decorator: wrapper_two 3 | Decorator function two: before 4 | enter decorator: wrapper_one 5 | Decorator function one: before 6 | Inside simple function 7 | Decorator function one: after 8 | exit decorator: wrapper_one 9 | Decorator function two: after 10 | exit decorator: wrapper_two 11 | 2 12 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/decorators/nested_decorator/program.py: -------------------------------------------------------------------------------- 1 | def decorator_function_one(func): 2 | def wrapper_one(): 3 | print("Decorator function one: before") 4 | result = func() 5 | print("Decorator function one: after") 6 | return result + 1 7 | return wrapper_one 8 | 9 | def decorator_function_two(func): 10 | def wrapper_two(): 11 | print("Decorator function two: before") 12 | result = func() 13 | print("Decorator function two: after") 14 | return result 15 | return wrapper_two 16 | 17 | @decorator_function_two 18 | @decorator_function_one 19 | def simple_function(): 20 | print("Inside simple function") 21 | return 1 22 | 23 | print(simple_function()) 24 | 25 | -------------------------------------------------------------------------------- /tests/trace_single_hook/elif/analysis.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional 2 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 3 | from dynapyt.instrument.IIDs import IIDs 4 | 5 | 6 | class TestAnalysis(BaseAnalysis): 7 | def begin_execution(self) -> None: 8 | print("begin execution") 9 | 10 | def enter_control_flow(self, dyn_ast: str, iid: int, cond_value: bool) -> Optional[bool]: 11 | print(f"enter control flow event with condition {cond_value}") 12 | 13 | def enter_if(self, dyn_ast: str, iid: int, cond_value: bool) -> Optional[bool]: 14 | print(f"if condition evaluates to {cond_value}") 15 | 16 | def exit_if(self, dyn_ast: str, iid: int): 17 | print(f"done with if statement") 18 | 19 | def exit_control_flow(self, dyn_ast: str, iid: int) -> None: 20 | print(f"done with control flow statement") 21 | 22 | def end_execution(self) -> None: 23 | print("end execution") 24 | -------------------------------------------------------------------------------- /tests/trace_single_hook/elif/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | enter control flow event with condition False 3 | if condition evaluates to False 4 | enter control flow event with condition False 5 | if condition evaluates to False 6 | enter control flow event with condition True 7 | if condition evaluates to True 8 | foo is equal to 50 9 | done with control flow statement 10 | done with if statement 11 | enter control flow event with condition False 12 | if condition evaluates to False 13 | enter control flow event with condition False 14 | if condition evaluates to False 15 | done with control flow statement 16 | done with if statement 17 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/elif/program.py: -------------------------------------------------------------------------------- 1 | foo = 45 + 5 2 | if foo > 50: 3 | print("foo is greater than 50") 4 | elif foo == 45: 5 | print("foo is equal to 45") 6 | elif foo == 50: 7 | print("foo is equal to 50") 8 | else: 9 | print("foo is less than 50") 10 | 11 | if foo % 3 == 0: 12 | print("foo is divisible by 3") 13 | elif foo % 3 == 1: 14 | print("foo is 1 more than a multiple of 3") 15 | -------------------------------------------------------------------------------- /tests/trace_single_hook/for_comprehension/analysis.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Iterable 2 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 3 | 4 | 5 | class TestAnalysis(BaseAnalysis): 6 | def begin_execution(self) -> None: 7 | print("begin execution") 8 | 9 | def enter_control_flow( 10 | self, dyn_ast: str, iid: int, cond_value: bool 11 | ) -> Optional[bool]: 12 | print(f"enter control flow event with condition {cond_value}") 13 | 14 | def enter_for( 15 | self, dyn_ast: str, iid: int, next_value: bool, iterable: Iterable 16 | ) -> Optional[bool]: 17 | print(f"for condition evaluates to {next_value}") 18 | 19 | def exit_for(self, dyn_ast: str, iid: int): 20 | print(f"done with for statement") 21 | 22 | def exit_control_flow(self, dyn_ast: str, iid: int) -> None: 23 | print(f"done with control flow statement") 24 | 25 | def end_execution(self) -> None: 26 | print("end execution") 27 | -------------------------------------------------------------------------------- /tests/trace_single_hook/for_comprehension/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | enter control flow event with condition True 3 | for condition evaluates to 0 4 | enter control flow event with condition True 5 | for condition evaluates to 1 6 | enter control flow event with condition True 7 | for condition evaluates to 2 8 | enter control flow event with condition True 9 | for condition evaluates to 3 10 | enter control flow event with condition True 11 | for condition evaluates to 4 12 | enter control flow event with condition True 13 | for condition evaluates to 5 14 | enter control flow event with condition True 15 | for condition evaluates to 6 16 | enter control flow event with condition True 17 | for condition evaluates to 7 18 | enter control flow event with condition True 19 | for condition evaluates to 8 20 | enter control flow event with condition True 21 | for condition evaluates to 9 22 | enter control flow event with condition False 23 | for condition evaluates to 24 | done with control flow statement 25 | done with for statement 26 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/for_comprehension/program.py: -------------------------------------------------------------------------------- 1 | [i for i in range(10)] 2 | -------------------------------------------------------------------------------- /tests/trace_single_hook/if/analysis.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional 2 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 3 | from dynapyt.instrument.IIDs import IIDs 4 | 5 | 6 | class TestAnalysis(BaseAnalysis): 7 | def begin_execution(self) -> None: 8 | print("begin execution") 9 | 10 | def enter_control_flow(self, dyn_ast: str, iid: int, cond_value: bool) -> Optional[bool]: 11 | print(f"enter control flow event with condition {cond_value}") 12 | 13 | def enter_if(self, dyn_ast: str, iid: int, cond_value: bool) -> Optional[bool]: 14 | print(f"if condition evaluates to {cond_value}") 15 | 16 | def exit_if(self, dyn_ast: str, iid: int): 17 | print(f"done with if statement") 18 | 19 | def exit_control_flow(self, dyn_ast: str, iid: int) -> None: 20 | print(f"done with control flow statement") 21 | 22 | def end_execution(self) -> None: 23 | print("end execution") 24 | -------------------------------------------------------------------------------- /tests/trace_single_hook/if/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | enter control flow event with condition True 3 | if condition evaluates to True 4 | foo is greater than 50 5 | done with control flow statement 6 | done with if statement 7 | enter control flow event with condition False 8 | if condition evaluates to False 9 | foo is even 10 | done with control flow statement 11 | done with if statement 12 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/if/program.py: -------------------------------------------------------------------------------- 1 | foo = 45 + 23 2 | if foo > 50: 3 | print("foo is greater than 50") 4 | else: 5 | print("foo is less than or equal to 50") 6 | 7 | if foo % 2 == 1: 8 | print("foo is odd") 9 | else: 10 | print("foo is even") 11 | -------------------------------------------------------------------------------- /tests/trace_single_hook/if_expression/analysis.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional 2 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 3 | from dynapyt.instrument.IIDs import IIDs 4 | 5 | 6 | class TestAnalysis(BaseAnalysis): 7 | def begin_execution(self) -> None: 8 | print("begin execution") 9 | 10 | def enter_control_flow( 11 | self, dyn_ast: str, iid: int, cond_value: bool 12 | ) -> Optional[bool]: 13 | print(f"enter control flow event with condition {cond_value}") 14 | 15 | def enter_if(self, dyn_ast: str, iid: int, cond_value: bool) -> Optional[bool]: 16 | print(f"if expression evaluates to {cond_value}") 17 | 18 | def exit_if(self, dyn_ast: str, iid: int): 19 | print(f"done with if expression") 20 | 21 | def exit_control_flow(self, dyn_ast: str, iid: int) -> None: 22 | print(f"done with control flow statement") 23 | 24 | def end_execution(self) -> None: 25 | print("end execution") 26 | -------------------------------------------------------------------------------- /tests/trace_single_hook/if_expression/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | enter control flow event with condition True 3 | if expression evaluates to True 4 | done with control flow statement 5 | done with if expression 6 | yes 7 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/if_expression/program.py: -------------------------------------------------------------------------------- 1 | foo = "yes" if 23 == 23 else "no" 2 | print(foo) 3 | -------------------------------------------------------------------------------- /tests/trace_single_hook/if_expression_side_effects/analysis.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional 2 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 3 | from dynapyt.instrument.IIDs import IIDs 4 | 5 | 6 | class TestAnalysis(BaseAnalysis): 7 | def begin_execution(self) -> None: 8 | print("begin execution") 9 | 10 | def enter_control_flow( 11 | self, dyn_ast: str, iid: int, cond_value: bool 12 | ) -> Optional[bool]: 13 | print(f"enter control flow event with condition {cond_value}") 14 | 15 | def enter_if(self, dyn_ast: str, iid: int, cond_value: bool) -> Optional[bool]: 16 | print(f"if expression evaluates to {cond_value}") 17 | 18 | def exit_if(self, dyn_ast: str, iid: int): 19 | print(f"done with if expression") 20 | 21 | def exit_control_flow(self, dyn_ast: str, iid: int) -> None: 22 | print(f"done with control flow statement") 23 | 24 | def end_execution(self) -> None: 25 | print("end execution") 26 | -------------------------------------------------------------------------------- /tests/trace_single_hook/if_expression_side_effects/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | enter control flow event with condition True 3 | if expression evaluates to True 4 | bar yes 5 | done with control flow statement 6 | done with if expression 7 | yes 8 | enter control flow event with condition False 9 | if expression evaluates to False 10 | bar no 11 | done with control flow statement 12 | done with if expression 13 | no 14 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/if_expression_side_effects/program.py: -------------------------------------------------------------------------------- 1 | def bar(x): 2 | print(f"bar {x}") 3 | return x 4 | 5 | 6 | foo = bar("yes") if 23 == 23 else bar("no") 7 | print(foo) 8 | foo = bar("yes") if 20 == 23 else bar("no") 9 | print(foo) 10 | -------------------------------------------------------------------------------- /tests/trace_single_hook/in/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | 4 | class TestAnalysis(BaseAnalysis): 5 | def _in(self, dyn_ast, iid, left, right, result): 6 | print(f"Checking if {left} is in {right} --> {result}") 7 | -------------------------------------------------------------------------------- /tests/trace_single_hook/in/expected.txt: -------------------------------------------------------------------------------- 1 | Checking if 2 is in [1, 2, 3] --> True 2 | 2 is in l 3 | 1 4 | 2 5 | 3 -------------------------------------------------------------------------------- /tests/trace_single_hook/in/program.py: -------------------------------------------------------------------------------- 1 | l = [1, 2, 3] 2 | if 2 in l: 3 | print("2 is in l") 4 | for i in l: 5 | print(i) 6 | -------------------------------------------------------------------------------- /tests/trace_single_hook/lhs_attr/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | 4 | class TestAnalysis(BaseAnalysis): 5 | def read_attribute(self, dyn_ast, iid, base, attr, value): 6 | print(f"Reading attribute {attr} from {base} with value {value}") 7 | -------------------------------------------------------------------------------- /tests/trace_single_hook/lhs_attr/expected.txt: -------------------------------------------------------------------------------- 1 | Reading attribute a from > with value 2 2 | 2 3 | Reading attribute b from > with value hello 4 | hello -------------------------------------------------------------------------------- /tests/trace_single_hook/lhs_attr/program.py: -------------------------------------------------------------------------------- 1 | class X: 2 | a = 1 3 | 4 | 5 | x = X() 6 | x.a += 1 7 | print(x.a) 8 | x.b: str = "hello" 9 | print(x.b) 10 | -------------------------------------------------------------------------------- /tests/trace_single_hook/list/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | 4 | class TestAnalysis(BaseAnalysis): 5 | def _list(self, dyn_ast, iid, value): 6 | print("List") 7 | -------------------------------------------------------------------------------- /tests/trace_single_hook/list/expected.txt: -------------------------------------------------------------------------------- 1 | List 2 | [0, 2, 4, 6, 8, 10, 12, 14, 16, 18] -------------------------------------------------------------------------------- /tests/trace_single_hook/list/program.py: -------------------------------------------------------------------------------- 1 | l = [i * 2 for i in range(10)] 2 | print(l) 3 | -------------------------------------------------------------------------------- /tests/trace_single_hook/literals/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | 4 | class TestAnalysis(BaseAnalysis): 5 | def begin_execution(self): 6 | print("begin execution") 7 | 8 | def end_execution(self): 9 | print("end execution") 10 | 11 | def literal(self, dyn_ast, iid, value): 12 | print(f"literal {value}") 13 | -------------------------------------------------------------------------------- /tests/trace_single_hook/literals/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | literal 1 3 | literal 1.2 4 | literal hello 5 | literal True 6 | literal 1 7 | literal 2 8 | literal 3 9 | literal [1, 2, 3] 10 | literal 1 11 | literal 2 12 | literal 3 13 | literal (1, 2, 3) 14 | literal 1 15 | literal 2 16 | literal 3 17 | literal {1, 2, 3} 18 | literal a 19 | literal 1 20 | literal b 21 | literal 2 22 | literal {'a': 1, 'b': 2} 23 | literal None 24 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/literals/program.py: -------------------------------------------------------------------------------- 1 | a = 1 2 | b = 1.2 3 | c = "hello" 4 | d = True 5 | e = [1, 2, 3] 6 | f = (1, 2, 3) 7 | g = {1, 2, 3} 8 | h = {"a": 1, "b": 2} 9 | i = None 10 | -------------------------------------------------------------------------------- /tests/trace_single_hook/multiply/analysis.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional 2 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 3 | from dynapyt.instrument.IIDs import IIDs 4 | 5 | 6 | class TestAnalysis(BaseAnalysis): 7 | def multiply(self, dyn_ast: str, iid: int, left: Any, right: Any, result: Any) -> Any: 8 | print(f"multiplying {left} and {right} gives {result}") 9 | -------------------------------------------------------------------------------- /tests/trace_single_hook/multiply/expected.txt: -------------------------------------------------------------------------------- 1 | multiplying 5 and 6 gives 30 2 | 30 -------------------------------------------------------------------------------- /tests/trace_single_hook/multiply/program.py: -------------------------------------------------------------------------------- 1 | x = 5 * 6 2 | print(x) -------------------------------------------------------------------------------- /tests/trace_single_hook/simple_delete/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | 4 | class TestAnalysis(BaseAnalysis): 5 | def begin_execution(self) -> None: 6 | print("begin execution") 7 | 8 | def delete(self, dyn_ast, iid, value): 9 | print(f"deleting variable {value[0][1][0]}") 10 | 11 | def end_execution(self) -> None: 12 | print("end execution") 13 | -------------------------------------------------------------------------------- /tests/trace_single_hook/simple_delete/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | 10 3 | deleting variable a 4 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/simple_delete/program.py: -------------------------------------------------------------------------------- 1 | a = 10 2 | print(a) 3 | del a 4 | -------------------------------------------------------------------------------- /tests/trace_single_hook/simple_read/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | class TestAnalysis(BaseAnalysis): 4 | def begin_execution(self) -> None: 5 | print("begin execution") 6 | 7 | def read(self, dyn_ast, iid, value): 8 | print(f"read value {value}") 9 | 10 | def end_execution(self) -> None: 11 | print("end execution") -------------------------------------------------------------------------------- /tests/trace_single_hook/simple_read/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | read value 10 3 | 10 4 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/simple_read/program.py: -------------------------------------------------------------------------------- 1 | a = 10 2 | print(a) -------------------------------------------------------------------------------- /tests/trace_single_hook/string_literal/analysis.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 3 | from dynapyt.instrument.IIDs import IIDs 4 | 5 | 6 | class TestAnalysis(BaseAnalysis): 7 | def begin_execution(self) -> None: 8 | print("begin execution") 9 | 10 | def string(self, dyn_ast: str, iid: int, val: Any) -> Any: 11 | print(f'string literal "{val}"') 12 | 13 | def end_execution(self) -> None: 14 | print("end execution") 15 | -------------------------------------------------------------------------------- /tests/trace_single_hook/string_literal/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | string literal "abc" 3 | 29 4 | end execution 5 | -------------------------------------------------------------------------------- /tests/trace_single_hook/string_literal/program.py: -------------------------------------------------------------------------------- 1 | x = "abc" 2 | # Also test formatted literal 3 | x = f"{x}def" 4 | y = 23 5 | z = len(x) + y 6 | print(z) 7 | -------------------------------------------------------------------------------- /tests/trace_single_hook/try/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | class TestAnalysis(BaseAnalysis): 4 | def begin_execution(self) -> None: 5 | print("begin execution") 6 | 7 | def enter_try(self, dyn_ast: str, iid: int) -> None: 8 | print(f"entered try block") 9 | 10 | def end_execution(self) -> None: 11 | print("end execution") -------------------------------------------------------------------------------- /tests/trace_single_hook/try/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | entered try block 3 | print statement inside try block 4 | an exception occurred 5 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/try/program.py: -------------------------------------------------------------------------------- 1 | try: 2 | print("print statement inside try block") 3 | raise Exception("Exception") 4 | except: 5 | print("an exception occurred") -------------------------------------------------------------------------------- /tests/trace_single_hook/try_clean_exit/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | class TestAnalysis(BaseAnalysis): 4 | def begin_execution(self) -> None: 5 | print("begin execution") 6 | 7 | def enter_try(self, dyn_ast: str, iid: int) -> None: 8 | print(f"entered try block") 9 | 10 | def clean_exit_try(self, dyn_ast: str, iid: int) -> None: 11 | print(f"clean exit try block") 12 | 13 | def end_execution(self) -> None: 14 | print("end execution") -------------------------------------------------------------------------------- /tests/trace_single_hook/try_clean_exit/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | entered try block 3 | print statement inside try block 4 | 10 5 | clean exit try block 6 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/try_clean_exit/program.py: -------------------------------------------------------------------------------- 1 | x = 10 2 | try: 3 | print("print statement inside try block") 4 | print(x) 5 | except: 6 | print("an exception occurred") -------------------------------------------------------------------------------- /tests/trace_single_hook/try_control_flow_event/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | class TestAnalysis(BaseAnalysis): 4 | def begin_execution(self) -> None: 5 | print("begin execution") 6 | 7 | def control_flow_event(self, dyn_ast: str, iid: int) -> None: 8 | print(f"control flow event") 9 | 10 | def enter_try(self, dyn_ast: str, iid: int) -> None: 11 | print(f"entered try block") 12 | 13 | def clean_exit_try(self, dyn_ast: str, iid: int) -> None: 14 | print(f"clean exit try block") 15 | 16 | def end_execution(self) -> None: 17 | print("end execution") -------------------------------------------------------------------------------- /tests/trace_single_hook/try_control_flow_event/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | control flow event 3 | entered try block 4 | control flow event 5 | print statement inside try block 6 | control flow event 7 | 10 8 | control flow event 9 | clean exit try block 10 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/try_control_flow_event/program.py: -------------------------------------------------------------------------------- 1 | x = 10 2 | try: 3 | print("print statement inside try block") 4 | print(x) 5 | except: 6 | print("an exception occurred") -------------------------------------------------------------------------------- /tests/trace_single_hook/version_write/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.0.1" 2 | -------------------------------------------------------------------------------- /tests/trace_single_hook/version_write/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | 4 | class TestAnalysis(BaseAnalysis): 5 | def begin_execution(self) -> None: 6 | print("begin execution") 7 | 8 | def write(self, dyn_ast, iid, old_vals, new_val): 9 | print(f"write value {new_val}") 10 | 11 | def end_execution(self) -> None: 12 | print("end execution") 13 | -------------------------------------------------------------------------------- /tests/trace_single_hook/version_write/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | write value 0.0.1 3 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/version_write/program.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | with open(os.sep.join(__file__.split(os.sep)[:-1]) + f"{os.sep}__init__.py", "r") as f: 4 | exec(f.read()) 5 | -------------------------------------------------------------------------------- /tests/trace_single_hook/while/analysis.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 3 | 4 | class TestAnalysis(BaseAnalysis): 5 | def begin_execution(self) -> None: 6 | print("begin execution") 7 | 8 | def enter_control_flow(self, dyn_ast: str, iid: int, cond_value: bool) -> Optional[bool]: 9 | print(f"enter control flow event with condition {cond_value}") 10 | 11 | def enter_while(self, dyn_ast: str, iid: int, cond_value: bool) -> Optional[bool]: 12 | print(f"while condition evaluates to {cond_value}") 13 | 14 | def exit_while(self, dyn_ast: str, iid: int): 15 | print(f"done with while statement") 16 | 17 | def exit_control_flow(self, dyn_ast: str, iid: int) -> None: 18 | print(f"done with control flow statement") 19 | 20 | def end_execution(self) -> None: 21 | print("end execution") 22 | 23 | -------------------------------------------------------------------------------- /tests/trace_single_hook/while/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | enter control flow event with condition True 3 | while condition evaluates to True 4 | 9 5 | enter control flow event with condition True 6 | while condition evaluates to True 7 | 8 8 | enter control flow event with condition True 9 | while condition evaluates to True 10 | 7 11 | enter control flow event with condition True 12 | while condition evaluates to True 13 | 6 14 | enter control flow event with condition True 15 | while condition evaluates to True 16 | 5 17 | enter control flow event with condition True 18 | while condition evaluates to True 19 | 4 20 | enter control flow event with condition True 21 | while condition evaluates to True 22 | 3 23 | enter control flow event with condition True 24 | while condition evaluates to True 25 | 2 26 | enter control flow event with condition True 27 | while condition evaluates to True 28 | 1 29 | enter control flow event with condition True 30 | while condition evaluates to True 31 | 0 32 | enter control flow event with condition False 33 | while condition evaluates to False 34 | done! 35 | done with control flow statement 36 | done with while statement 37 | enter control flow event with condition True 38 | while condition evaluates to True 39 | 9 40 | enter control flow event with condition True 41 | done with control flow statement 42 | done with while statement 43 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/while/program.py: -------------------------------------------------------------------------------- 1 | i = 10 2 | while i > 0: 3 | i -= 1 4 | print(i) 5 | else: 6 | print("done!") 7 | 8 | i = 10 9 | while i > 0: 10 | i -= 1 11 | print(i) 12 | if i % 3 == 0: 13 | break 14 | else: 15 | print("done!") 16 | -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | class TestAnalysis(BaseAnalysis): 4 | def begin_execution(self) -> None: 5 | print("begin execution") 6 | 7 | def enter_with(self, dyn_ast: str, iid: int, ctx_manager): 8 | print(f"with statement entered") 9 | 10 | def exit_with(self, dyn_ast: str, iid: int, is_suppressed: bool, exc_value): 11 | print(f"with statement exited") 12 | 13 | def end_execution(self) -> None: 14 | print("end execution") -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | with statement entered 3 | Line read from expected.txt: begin execution 4 | 5 | content has been read from the file 6 | with statement exited 7 | file has been closed 8 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with/program.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | dir_name = os.path.dirname(os.path.realpath(__file__)) 4 | file_path = os.path.join(dir_name, "expected.txt") 5 | with open(file_path, "r") as file: 6 | content = file.readline() 7 | print("Line read from expected.txt: ", content) 8 | print("content has been read from the file") 9 | 10 | print("file has been closed") 11 | -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with_multiple_items/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | class TestAnalysis(BaseAnalysis): 4 | def begin_execution(self) -> None: 5 | print("begin execution") 6 | 7 | def enter_with(self, dyn_ast: str, iid: int, ctx_manager): 8 | print(f"with statement entered") 9 | 10 | def exit_with(self, dyn_ast: str, iid: int, is_suppressed: bool, exc_value): 11 | print(f"with statement exited") 12 | 13 | def end_execution(self) -> None: 14 | print("end execution") -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with_multiple_items/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | with statement entered 3 | with statement entered 4 | Line read from expected.txt: begin execution 5 | 6 | Line read from analysis.py: from dynapyt.analyses.BaseAnalysis import BaseAnalysis 7 | 8 | content has been read from the files 9 | with statement exited 10 | with statement exited 11 | file has been closed 12 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with_multiple_items/program.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | dir_name = os.path.dirname(os.path.realpath(__file__)) 4 | file_path_1 = os.path.join(dir_name, "expected.txt") 5 | file_path_2 = os.path.join(dir_name, "analysis.py") 6 | with open(file_path_1, "r") as file1, open(file_path_2, "r") as file2: 7 | content = file1.readline() 8 | print("Line read from expected.txt: ", content) 9 | content = file2.readline() 10 | print("Line read from analysis.py: ", content) 11 | print("content has been read from the files") 12 | 13 | print("file has been closed") 14 | -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with_multiple_items_as_specifier_case_1/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | class TestAnalysis(BaseAnalysis): 4 | def begin_execution(self) -> None: 5 | print("begin execution") 6 | 7 | def enter_with(self, dyn_ast: str, iid: int, ctx_manager): 8 | print(f"with statement entered") 9 | 10 | def exit_with(self, dyn_ast: str, iid: int, is_suppressed: bool, exc_value): 11 | print(f"with statement exited") 12 | 13 | def end_execution(self) -> None: 14 | print("end execution") -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with_multiple_items_as_specifier_case_1/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | with statement entered 3 | with statement entered 4 | Line read from expected.txt: begin execution 5 | 6 | Line read from analysis.py: from dynapyt.analyses.BaseAnalysis import BaseAnalysis 7 | 8 | content has been read from the files 9 | with statement exited 10 | with statement exited 11 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with_multiple_items_as_specifier_case_1/program.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | dir_name = os.path.dirname(os.path.realpath(__file__)) 4 | file_path_one = os.path.join(dir_name, "expected.txt") 5 | file_path_two = os.path.join(dir_name, "analysis.py") 6 | 7 | file1 = open(file_path_one, "r") 8 | file2 = open(file_path_two, "r") 9 | with file1, file2: 10 | content = file1.readline() 11 | print("Line read from expected.txt: " + content) 12 | content = file2.readline() 13 | print("Line read from analysis.py: " + content) 14 | print("content has been read from the files") 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with_multiple_items_as_specifier_case_2/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | class TestAnalysis(BaseAnalysis): 4 | def begin_execution(self) -> None: 5 | print("begin execution") 6 | 7 | def enter_with(self, dyn_ast: str, iid: int, ctx_manager): 8 | print(f"with statement entered") 9 | 10 | def exit_with(self, dyn_ast: str, iid: int, is_suppressed: bool, exc_value): 11 | print(f"with statement exited") 12 | 13 | def end_execution(self) -> None: 14 | print("end execution") -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with_multiple_items_as_specifier_case_2/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | with statement entered 3 | with statement entered 4 | with statement entered 5 | Line read from expected.txt: begin execution 6 | 7 | Line read from expected.txt: begin execution 8 | 9 | Line read from analysis.py: from dynapyt.analyses.BaseAnalysis import BaseAnalysis 10 | 11 | content has been read from the files 12 | with statement exited 13 | with statement exited 14 | with statement exited 15 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with_multiple_items_as_specifier_case_2/program.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | dir_name = os.path.dirname(os.path.realpath(__file__)) 4 | file_path_one = os.path.join(dir_name, "expected.txt") 5 | file_path_two = os.path.join(dir_name, "expected.txt") 6 | file_path_three = os.path.join(dir_name, "analysis.py") 7 | 8 | file1 = open(file_path_one, "r") 9 | file3 = open(file_path_three, "r") 10 | with file1, open(file_path_two, "r") as file2, file3: 11 | content = file1.readline() 12 | print("Line read from expected.txt: " + content) 13 | content = file2.readline() 14 | print("Line read from expected.txt: " + content) 15 | content = file3.readline() 16 | print("Line read from analysis.py: " + content) 17 | print("content has been read from the files") 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with_multiple_items_suppress_exception/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | 4 | class TestAnalysis(BaseAnalysis): 5 | def begin_execution(self) -> None: 6 | print("begin execution") 7 | 8 | def enter_with(self, dyn_ast: str, iid: int, ctx_manager): 9 | print(f"with statement entered") 10 | 11 | def exit_with(self, dyn_ast: str, iid: int, is_suppressed: bool, exc_value): 12 | print(f"with statement exited") 13 | 14 | def end_execution(self) -> None: 15 | print("end execution") -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with_multiple_items_suppress_exception/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | with statement entered 3 | ContextManagerOne: __enter__ 4 | with statement entered 5 | ContextManagerTwo: __enter__ 6 | inside with block 7 | context manager one 8 | context manager two 9 | ContextManagerTwo: __exit__ 10 | exc_type: 11 | exc_value: exception raised inside with statement block 12 | with statement exited 13 | ContextManagerOne: __exit__ 14 | exc_type: None 15 | exc_value: None 16 | with statement exited 17 | no exception raised 18 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with_multiple_items_suppress_exception/program.py: -------------------------------------------------------------------------------- 1 | class ContextManagerOne: 2 | 3 | def __enter__(self): 4 | print('ContextManagerOne: __enter__') 5 | return "context manager one" 6 | 7 | def __exit__(self, exc_type, exc_value, traceback): 8 | print('ContextManagerOne: __exit__') 9 | print("exc_type: ", exc_type) 10 | print("exc_value: ", exc_value) 11 | return True 12 | 13 | class ContextManagerTwo: 14 | 15 | def __enter__(self): 16 | print('ContextManagerTwo: __enter__') 17 | return "context manager two" 18 | 19 | def __exit__(self, exc_type, exc_value, traceback): 20 | print('ContextManagerTwo: __exit__') 21 | print("exc_type: ", exc_type) 22 | print("exc_value: ", exc_value) 23 | return True 24 | 25 | try : 26 | with ContextManagerOne() as cm1, ContextManagerTwo() as cm2: 27 | print('inside with block') 28 | print(cm1) 29 | print(cm2) 30 | raise Exception("exception raised inside with statement block") 31 | except Exception as e: 32 | print("exception caught: ", e) 33 | else: 34 | print("no exception raised") -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with_multiple_raise_exception/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | class TestAnalysis(BaseAnalysis): 4 | 5 | def begin_execution(self) -> None: 6 | print("begin execution") 7 | 8 | def enter_with(self, dyn_ast: str, iid: int, ctx_manager): 9 | print(f"with statement entered") 10 | 11 | def exit_with(self, dyn_ast: str, iid: int, is_suppressed: bool, exc_value): 12 | print(f"with statement exited") 13 | 14 | def end_execution(self) -> None: 15 | print("end execution") -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with_multiple_raise_exception/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | with statement entered 3 | ContextManagerOne: __enter__ 4 | with statement entered 5 | ContextManagerTwo: __enter__ 6 | inside with block 7 | context manager one 8 | context manager two 9 | ContextManagerTwo: __exit__ 10 | exc_type: 11 | exc_value: exception raised inside with statement block 12 | with statement exited 13 | ContextManagerOne: __exit__ 14 | exc_type: 15 | exc_value: exception raised inside with statement block 16 | with statement exited 17 | exception caught: exception raised inside with statement block 18 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with_multiple_raise_exception/program.py: -------------------------------------------------------------------------------- 1 | class ContextManagerOne: 2 | 3 | def __enter__(self): 4 | print('ContextManagerOne: __enter__') 5 | return "context manager one" 6 | 7 | def __exit__(self, exc_type, exc_value, traceback): 8 | print('ContextManagerOne: __exit__') 9 | print("exc_type: ", exc_type) 10 | print("exc_value: ", exc_value) 11 | 12 | class ContextManagerTwo: 13 | 14 | def __enter__(self): 15 | print('ContextManagerTwo: __enter__') 16 | return "context manager two" 17 | 18 | def __exit__(self, exc_type, exc_value, traceback): 19 | print('ContextManagerTwo: __exit__') 20 | print("exc_type: ", exc_type) 21 | print("exc_value: ", exc_value) 22 | 23 | try : 24 | with ContextManagerOne() as cm1, ContextManagerTwo() as cm2: 25 | print('inside with block') 26 | print(cm1) 27 | print(cm2) 28 | raise Exception("exception raised inside with statement block") 29 | except Exception as e: 30 | print("exception caught: ", e) 31 | else: 32 | print("no exception raised") -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with_nested_call/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | from typing import Callable, Tuple, Dict, Any 3 | 4 | class TestAnalysis(BaseAnalysis): 5 | def begin_execution(self) -> None: 6 | print("begin execution") 7 | 8 | def enter_with(self, dyn_ast: str, iid: int, ctx_manager): 9 | print(f"with statement entered") 10 | 11 | def exit_with(self, dyn_ast: str, iid: int, is_suppressed: bool, exc_value): 12 | print(f"with statement exited") 13 | 14 | def pre_call( 15 | self, dyn_ast: str, iid: int, function: Callable, pos_args: Tuple, kw_args: Dict 16 | ): 17 | print("pre call: ", function.__name__) 18 | 19 | 20 | def post_call( 21 | self, 22 | dyn_ast: str, 23 | iid: int, 24 | result: Any, 25 | function: Callable, 26 | pos_args: Tuple, 27 | kw_args: Dict, 28 | ) -> Any: 29 | print("post call: ", function.__name__) 30 | 31 | def end_execution(self) -> None: 32 | print("end execution") -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with_nested_call/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | pre call: foo 3 | pre call: realpath 4 | post call: realpath 5 | pre call: dirname 6 | post call: dirname 7 | pre call: join 8 | post call: join 9 | post call: foo 10 | pre call: open 11 | post call: open 12 | with statement entered 13 | pre call: readline 14 | post call: readline 15 | pre call: print 16 | Line read from expected.txt: begin execution 17 | 18 | post call: print 19 | pre call: print 20 | content has been read from the file 21 | post call: print 22 | with statement exited 23 | pre call: print 24 | file has been closed 25 | post call: print 26 | end execution 27 | -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with_nested_call/program.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | def foo(): 4 | dir_name = os.path.dirname(os.path.realpath(__file__)) 5 | file_path = os.path.join(dir_name, "expected.txt") 6 | return file_path 7 | 8 | with open(foo(), "r") as file: 9 | content = file.readline() 10 | print("Line read from expected.txt: " + content) 11 | print("content has been read from the file") 12 | 13 | print("file has been closed") -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with_raise_exception/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | class TestAnalysis(BaseAnalysis): 4 | 5 | def begin_execution(self) -> None: 6 | print("begin execution") 7 | 8 | def enter_with(self, dyn_ast: str, iid: int, ctx_manager): 9 | print(f"with statement entered") 10 | 11 | def exit_with(self, dyn_ast: str, iid: int, is_suppressed: bool, exc_value): 12 | print(f"with statement exited") 13 | 14 | def end_execution(self) -> None: 15 | print("end execution") -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with_raise_exception/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | with statement entered 3 | __enter__ 4 | inside with block 5 | hello world 6 | __exit__ 7 | exc_type: 8 | exc_value: exception raised inside with statement block 9 | with statement exited 10 | exception caught: exception raised inside with statement block 11 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with_raise_exception/program.py: -------------------------------------------------------------------------------- 1 | class ContextManager: 2 | 3 | def __enter__(self): 4 | print('__enter__') 5 | return "hello world" 6 | 7 | def __exit__(self, exc_type, exc_value, traceback): 8 | print('__exit__') 9 | print("exc_type: ", exc_type) 10 | print("exc_value: ", exc_value) 11 | 12 | try : 13 | with ContextManager() as cm: 14 | print('inside with block') 15 | print(cm) 16 | raise Exception("exception raised inside with statement block") 17 | except Exception as e: 18 | print("exception caught: ", e) 19 | else: 20 | print("no exception raised") -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with_runtime_control_flow_hooks/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | 4 | class TestAnalysis(BaseAnalysis): 5 | def begin_execution(self) -> None: 6 | print("begin execution") 7 | 8 | def enter_with(self, dyn_ast: str, iid: int, ctx_manager): 9 | print(f"with statement entered") 10 | 11 | def exit_with(self, dyn_ast: str, iid: int, is_suppressed: bool, exc_value): 12 | print(f"with statement exited") 13 | 14 | def runtime_event(self, dyn_ast: str, iid: int) -> None: 15 | print("runtime event") 16 | 17 | def control_flow_event(self, dyn_ast: str, iid: int) -> None: 18 | print("control flow event") 19 | 20 | def end_execution(self) -> None: 21 | print("end execution") -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with_runtime_control_flow_hooks/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | runtime event 3 | runtime event 4 | runtime event 5 | runtime event 6 | runtime event 7 | control flow event 8 | runtime event 9 | control flow event 10 | runtime event 11 | runtime event 12 | runtime event 13 | runtime event 14 | runtime event 15 | runtime event 16 | control flow event 17 | runtime event 18 | runtime event 19 | runtime event 20 | runtime event 21 | control flow event 22 | runtime event 23 | control flow event 24 | with statement entered 25 | runtime event 26 | runtime event 27 | runtime event 28 | control flow event 29 | runtime event 30 | runtime event 31 | runtime event 32 | runtime event 33 | control flow event 34 | Line read from expected.txt: begin execution 35 | 36 | runtime event 37 | runtime event 38 | control flow event 39 | content has been read from the file 40 | runtime event 41 | control flow event 42 | with statement exited 43 | runtime event 44 | runtime event 45 | control flow event 46 | file has been closed 47 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with_runtime_control_flow_hooks/program.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | dir_name = os.path.dirname(os.path.realpath(__file__)) 4 | file_path = os.path.join(dir_name, "expected.txt") 5 | with open(file_path, "r") as file: 6 | content = file.readline() 7 | print("Line read from expected.txt: ", content) 8 | print("content has been read from the file") 9 | 10 | print("file has been closed") 11 | -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with_suppress_exception/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | 4 | class TestAnalysis(BaseAnalysis): 5 | def begin_execution(self) -> None: 6 | print("begin execution") 7 | 8 | def enter_with(self, dyn_ast: str, iid: int, ctx_manager): 9 | print(f"with statement entered") 10 | 11 | def exit_with(self, dyn_ast: str, iid: int, is_suppressed: bool, exc_value): 12 | print(f"with statement exited") 13 | 14 | def end_execution(self) -> None: 15 | print("end execution") -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with_suppress_exception/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | with statement entered 3 | __enter__ 4 | inside with block 5 | hello world 6 | __exit__ 7 | exc_type: 8 | exc_value: exception raised inside with statement block 9 | with statement exited 10 | no exception raised 11 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with_suppress_exception/program.py: -------------------------------------------------------------------------------- 1 | class ContextManager: 2 | def __enter__(self): 3 | print('__enter__') 4 | return "hello world" 5 | 6 | def __exit__(self, exc_type, exc_value, traceback): 7 | print('__exit__') 8 | print("exc_type: ", exc_type) 9 | print("exc_value: ", exc_value) 10 | return True 11 | 12 | try : 13 | with ContextManager() as cm: 14 | print('inside with block') 15 | print(cm) 16 | raise Exception("exception raised inside with statement block") 17 | except Exception as e: 18 | print("exception caught: ", e) 19 | else: 20 | print("no exception raised") -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with_with_attribute_call/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | from typing import Any, Callable, Dict, Tuple 3 | 4 | 5 | class TestAnalysis(BaseAnalysis): 6 | 7 | def begin_execution(self) -> None: 8 | print("begin execution") 9 | 10 | def enter_with(self, dyn_ast: str, iid: int, ctx_manager): 11 | print(f"with statement entered") 12 | 13 | def exit_with(self, dyn_ast: str, iid: int, is_suppressed: bool, exc_value): 14 | print(f"with statement exited") 15 | 16 | def pre_call( 17 | self, dyn_ast: str, iid: int, function: Callable, pos_args: Tuple, kw_args: Dict 18 | ): 19 | print("pre call: ", function.__name__) 20 | 21 | def post_call( 22 | self, 23 | dyn_ast: str, 24 | iid: int, 25 | result: Any, 26 | function: Callable, 27 | pos_args: Tuple, 28 | kw_args: Dict, 29 | ) -> Any: 30 | print("post call: ", function.__name__) 31 | 32 | def end_execution(self) -> None: 33 | print("end execution") -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with_with_attribute_call/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | pre call: Foo 3 | post call: Foo 4 | pre call: bar 5 | post call: bar 6 | with statement entered 7 | pre call: print 8 | hello world 9 | post call: print 10 | with statement exited 11 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with_with_attribute_call/program.py: -------------------------------------------------------------------------------- 1 | 2 | from contextlib import contextmanager 3 | 4 | class Foo: 5 | @contextmanager 6 | def bar(self): 7 | yield "hello world" 8 | 9 | foo = Foo() 10 | with foo.bar() as cm: 11 | print(cm) 12 | -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with_without_as_specifier/analysis.py: -------------------------------------------------------------------------------- 1 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 2 | 3 | class TestAnalysis(BaseAnalysis): 4 | def begin_execution(self) -> None: 5 | print("begin execution") 6 | 7 | def enter_with(self, dyn_ast: str, iid: int, ctx_manager): 8 | print(f"with statement entered") 9 | 10 | def exit_with(self, dyn_ast: str, iid: int, is_suppressed: bool, exc_value): 11 | print(f"with statement exited") 12 | 13 | def end_execution(self) -> None: 14 | print("end execution") -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with_without_as_specifier/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | with statement entered 3 | Line read from expected.txt: begin execution 4 | 5 | content has been read from the file 6 | with statement exited 7 | An error occurred: I/O operation on closed file. 8 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/with/with_without_as_specifier/program.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | try: 4 | dir_name = os.path.dirname(os.path.realpath(__file__)) 5 | file_path = os.path.join(dir_name, "expected.txt") 6 | file = open(file_path, "r") 7 | with file: 8 | content = file.readline() 9 | print("Line read from expected.txt: " + content) 10 | print("content has been read from the file") 11 | 12 | file.readline() 13 | except Exception as e: 14 | print("An error occurred: ", e) 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /tests/trace_single_hook/write/analysis.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 3 | 4 | 5 | class TestAnalysis(BaseAnalysis): 6 | def begin_execution(self) -> None: 7 | print("begin execution") 8 | 9 | def end_execution(self) -> None: 10 | print("end execution") 11 | 12 | def write(self, dyn_ast, iid, old_values, new_value): 13 | print(f"Writing {new_value} to {[ov.__code__.co_names for ov in old_values]}") 14 | -------------------------------------------------------------------------------- /tests/trace_single_hook/write/expected.txt: -------------------------------------------------------------------------------- 1 | begin execution 2 | Writing 10 to [('a',)] 3 | Writing test to [('b',)] 4 | Writing testtesttesttesttesttesttesttesttesttest to [('c',)] 5 | Writing testing to [('b',)] 6 | Writing 30 to [('a',)] 7 | Writing ('test',) to [('d',)] 8 | Writing ('test',) to [('e',)] 9 | end execution -------------------------------------------------------------------------------- /tests/trace_single_hook/write/program.py: -------------------------------------------------------------------------------- 1 | a = 10 2 | b = "test" 3 | c = a * b 4 | b += "ing" 5 | a *= 3 6 | d = "test", 7 | e: str = "test", 8 | -------------------------------------------------------------------------------- /tutorial/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12-slim 2 | 3 | WORKDIR /dynapyt 4 | 5 | COPY . . -------------------------------------------------------------------------------- /tutorial/solution-analyses/BranchCoverageAnalysis.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 3 | 4 | 5 | class BranchCoverageAnalysis(BaseAnalysis): 6 | def __init__(self): 7 | self.branches = dict() 8 | 9 | def enter_control_flow( 10 | self, dyn_ast: str, iid: int, cond_value: bool 11 | ) -> Optional[bool]: 12 | self.branches[(iid, bool(cond_value))] = ( 13 | self.branches.get((iid, bool(cond_value)), 0) + 1 14 | ) 15 | 16 | def end_execution(self): 17 | for k, v in self.branches.items(): 18 | print( 19 | f'Branch {k[0]} taken with condition {k[1]}, {v} time{"" if v == 1 else "s"}' 20 | ) 21 | -------------------------------------------------------------------------------- /tutorial/solution-analyses/OppositeBranchAnalysis.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 3 | 4 | 5 | class OppositeBranchAnalysis(BaseAnalysis): 6 | def enter_control_flow( 7 | self, dyn_ast: str, iid: int, cond_value: bool 8 | ) -> Optional[bool]: 9 | return not cond_value 10 | -------------------------------------------------------------------------------- /tutorial/solution-analyses/SlowStringConcatAnalysis.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable 2 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 3 | 4 | 5 | class SlowStringConcatAnalysis(BaseAnalysis): 6 | def __init__(self): 7 | super().__init__() 8 | self.in_loop = [] 9 | self.concat_count = [] 10 | self.threshold = 5 11 | 12 | def enter_for(self, dyn_ast: str, iid: int, next_value, iterable: Iterable): 13 | if ( 14 | self.in_loop 15 | and self.in_loop[-1][0] == dyn_ast 16 | and self.in_loop[-1][1] == iid 17 | ): 18 | pass 19 | else: 20 | self.in_loop.append((dyn_ast, iid)) 21 | self.concat_count.append(0) 22 | 23 | def exit_for(self, dyn_ast: str, iid: int): 24 | curr = self.in_loop.pop() 25 | assert curr[0] == dyn_ast 26 | assert curr[1] == iid 27 | self.concat_count.pop() 28 | 29 | def add_assign(self, dyn_ast: str, iid: int, lhs, rhs): 30 | if self.in_loop: 31 | if isinstance(rhs, str): 32 | self.concat_count[-1] += 1 33 | if self.concat_count[-1] >= self.threshold: 34 | print(f"Possible slow string concatenation in {dyn_ast} at {iid}") 35 | -------------------------------------------------------------------------------- /tutorial/task1/main.py: -------------------------------------------------------------------------------- 1 | a = 1 2 | b = 2 3 | c = a + b 4 | if c >= 3: 5 | print("c >= 3") 6 | -------------------------------------------------------------------------------- /tutorial/task2/main.py: -------------------------------------------------------------------------------- 1 | a = 10 2 | if a < 20: 3 | b = 20 4 | while a < b: 5 | a += 1 6 | if a == 15: 7 | continue 8 | print(a) 9 | -------------------------------------------------------------------------------- /tutorial/task3/a.py: -------------------------------------------------------------------------------- 1 | def hello_world(): 2 | print("Hello World!") 3 | -------------------------------------------------------------------------------- /tutorial/task3/main.py: -------------------------------------------------------------------------------- 1 | import a 2 | 3 | 4 | def foo(): 5 | print("foo") 6 | 7 | 8 | def bar(): 9 | foo() 10 | a.hello_world() 11 | 12 | 13 | def baz(x: int): 14 | if x > 10: 15 | foo() 16 | else: 17 | bar() 18 | 19 | 20 | baz(5) 21 | -------------------------------------------------------------------------------- /tutorial/task4/main.py: -------------------------------------------------------------------------------- 1 | paragraph = """ 2 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. 3 | Sed non risus. Suspendisse lectus tortor, dignissim sit amet, 4 | adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. 5 | Maecenas ligula massa, varius a, semper congue, euismod non, mi. 6 | Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, 7 | non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, 8 | scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. 9 | Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum 10 | augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. 11 | Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum 12 | primis in faucibus orci luctus et ultrices posuere cubilia Curae; 13 | Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. 14 | Maecenas adipiscing ante non diam sodales hendrerit. 15 | """ 16 | 17 | lines = paragraph.split("\n") 18 | cleaned_lines = [line.strip() for line in lines] 19 | cleaned_paragraph = "" 20 | for line in cleaned_lines: 21 | cleaned_paragraph += line + "\n" 22 | -------------------------------------------------------------------------------- /tutorial/task5/main.py: -------------------------------------------------------------------------------- 1 | a = 30 2 | if a < 20: 3 | b = 20 4 | while a < b: 5 | a += 1 6 | if a > 15: 7 | continue 8 | elif a < 35: 9 | break 10 | print(a) 11 | else: 12 | print("a < 20") 13 | -------------------------------------------------------------------------------- /tutorial/tutorial-analyses/LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023-present Aryaz Eghbali 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /tutorial/tutorial-analyses/README.md: -------------------------------------------------------------------------------- 1 | # Tutorial Analyses 2 | 3 | [![PyPI - Version](https://img.shields.io/pypi/v/tutorial-analyses.svg)](https://pypi.org/project/tutorial-analyses) 4 | [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/tutorial-analyses.svg)](https://pypi.org/project/tutorial-analyses) 5 | 6 | ----- 7 | 8 | **Table of Contents** 9 | 10 | - [Installation](#installation) 11 | - [License](#license) 12 | 13 | ## Installation 14 | 15 | ```console 16 | pip install tutorial-analyses 17 | ``` 18 | 19 | ## License 20 | 21 | `tutorial-analyses` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license. 22 | -------------------------------------------------------------------------------- /tutorial/tutorial-analyses/src/tutorial_analyses/BranchCoverageAnalysis.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 3 | 4 | 5 | class BranchCoverageAnalysis(BaseAnalysis): 6 | def __init__(self, *args, **kwargs): 7 | super().__init__(*args, **kwargs) 8 | self.branches = {} 9 | 10 | def enter_control_flow(self, dyn_ast: str, iid: int, cond_value: bool): 11 | if (dyn_ast, iid, cond_value) not in self.branches: 12 | self.branches[(dyn_ast, iid, cond_value)] = 0 13 | self.branches[(dyn_ast, iid, cond_value)] += 1 14 | 15 | def end_execution(self): 16 | for k, v in self.branches.items(): 17 | print(f"Branch {k[1]} was taken with value {k[2]} {v} times") 18 | -------------------------------------------------------------------------------- /tutorial/tutorial-analyses/src/tutorial_analyses/OppositeBranchAnalysis.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 3 | 4 | 5 | class OppositeBranchAnalysis(BaseAnalysis): 6 | def enter_control_flow( 7 | self, dyn_ast: str, iid: int, cond_value: bool 8 | ) -> Optional[bool]: 9 | return not cond_value 10 | -------------------------------------------------------------------------------- /tutorial/tutorial-analyses/src/tutorial_analyses/SlowStringConcatAnalysis.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable 2 | from dynapyt.analyses.BaseAnalysis import BaseAnalysis 3 | 4 | 5 | class SlowStringConcatAnalysis(BaseAnalysis): 6 | def __init__(self, *args, **kwargs): 7 | super().__init__(*args, **kwargs) 8 | self.in_loop = [] 9 | self.concat_count = [] 10 | self.threshold = 5 11 | 12 | def enter_for(self, dyn_ast: str, iid: int, next_value, iterable: Iterable): 13 | if ( 14 | self.in_loop 15 | and self.in_loop[-1][0] == dyn_ast 16 | and self.in_loop[-1][1] == iid 17 | ): 18 | pass 19 | else: 20 | self.in_loop.append((dyn_ast, iid)) 21 | self.concat_count.append(0) 22 | 23 | def exit_for(self, dyn_ast: str, iid: int): 24 | curr = self.in_loop.pop() 25 | assert curr[0] == dyn_ast 26 | assert curr[1] == iid 27 | self.concat_count.pop() 28 | 29 | def add_assign(self, dyn_ast: str, iid: int, lhs, rhs): 30 | if self.in_loop: 31 | if isinstance(rhs, str): 32 | self.concat_count[-1] += 1 33 | if self.concat_count[-1] >= self.threshold: 34 | print(f"Possible slow string concatenation in {dyn_ast} at {iid}") 35 | -------------------------------------------------------------------------------- /tutorial/tutorial-analyses/src/tutorial_analyses/__about__.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023-present Aryaz Eghbali 2 | # 3 | # SPDX-License-Identifier: MIT 4 | __version__ = "0.0.1" 5 | -------------------------------------------------------------------------------- /tutorial/tutorial-analyses/src/tutorial_analyses/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023-present Aryaz Eghbali 2 | # 3 | # SPDX-License-Identifier: MIT 4 | -------------------------------------------------------------------------------- /tutorial/tutorial-analyses/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023-present Aryaz Eghbali 2 | # 3 | # SPDX-License-Identifier: MIT 4 | --------------------------------------------------------------------------------