From 2a8d82a2b779d692c8f522b9c27911beb6788346 Mon Sep 17 00:00:00 2001 From: ziad hany Date: Wed, 28 Oct 2026 19:34:37 +0300 Subject: [PATCH 1/9] Add support for the Atom tool Signed-off-by: ziadhany --- configure | 11 +- pyproject.toml | 4 +- setup.cfg | 32 +- src/README.rst | 2 - src/reachablecode/__init__.py | 0 src/reachablecode/pipelines/__init__.py | 0 .../pipelines/analyze_atom_reachability.py | 92 + src/reachablecode/pipes/__init__.py | 0 src/reachablecode/pipes/atom.py | 560 ++++++ tests/atom_reachability/python/app.py | 40 + ...reachability.python.reachables.slices.json | 1418 +++++++++++++++ ...tom_reachability.python.usages.slices.json | 1534 +++++++++++++++++ tests/test_atom_reachability_tools.py | 326 ++++ 13 files changed, 4001 insertions(+), 18 deletions(-) delete mode 100644 src/README.rst create mode 100644 src/reachablecode/__init__.py create mode 100644 src/reachablecode/pipelines/__init__.py create mode 100644 src/reachablecode/pipelines/analyze_atom_reachability.py create mode 100644 src/reachablecode/pipes/__init__.py create mode 100644 src/reachablecode/pipes/atom.py create mode 100644 tests/atom_reachability/python/app.py create mode 100644 tests/atom_reachability/python/atom_reachability.python.reachables.slices.json create mode 100644 tests/atom_reachability/python/atom_reachability.python.usages.slices.json create mode 100644 tests/test_atom_reachability_tools.py diff --git a/configure b/configure index 6d317d4..e111d23 100755 --- a/configure +++ b/configure @@ -28,14 +28,15 @@ CLI_ARGS=$1 ################################ # Requirement arguments passed to pip and used by default or with --dev. -REQUIREMENTS="--editable . --constraint requirements.txt" -DEV_REQUIREMENTS="--editable .[dev] --constraint requirements.txt --constraint requirements-dev.txt" +REQUIREMENTS="--editable ." +DEV_REQUIREMENTS="--editable .[testing,docs]" +DOCS_REQUIREMENTS="--editable .[docs]" # where we create a virtualenv VIRTUALENV_DIR=venv # Cleanable files and directories to delete with the --clean option -CLEANABLE="build dist venv .cache .eggs *.egg-info docs/_build/ pip-selfcheck.json" +CLEANABLE="build dist venv .cache .eggs" # extra arguments passed to pip PIP_EXTRA_ARGS=" " @@ -110,7 +111,7 @@ create_virtualenv() { fi $PYTHON_EXECUTABLE "$VIRTUALENV_PYZ" \ - --pip embed --setuptools embed \ + --wheel embed --pip embed --setuptools embed \ --seeder pip \ --never-download \ --no-periodic-update \ @@ -167,7 +168,6 @@ clean() { for cln in $CLEANABLE; do rm -rf "${CFG_ROOT_DIR:?}/${cln:?}"; done - find . -type f -name '*.py[co]' -delete -o -type d -name __pycache__ -delete set +e exit } @@ -185,6 +185,7 @@ while getopts :-: optchar; do help ) cli_help;; clean ) find_python && clean;; dev ) CFG_REQUIREMENTS="$DEV_REQUIREMENTS";; + docs ) CFG_REQUIREMENTS="$DOCS_REQUIREMENTS";; esac;; esac done diff --git a/pyproject.toml b/pyproject.toml index f106e69..3d500a0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,7 +68,7 @@ include = [ "." ] -# ignore test data and testfiles: they should never be linted nor formatted +# ignore test data and testfiles: they should never be linted nor formatted exclude = [ # main style "**/tests/data/**/*", @@ -126,4 +126,4 @@ max-complexity = 10 [tool.doc8] ignore-path = ["docs/build", "doc/build", "docs/_build", "doc/_build"] -max-line-length=100 +max-line-length=100 \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index a0f2985..4aa369a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,14 +1,14 @@ [metadata] -name = skeleton +name = reachablecode license = Apache-2.0 # description must be on ONE line https://github.com/pypa/setuptools/issues/1390 -description = skeleton +description = A collection of ScanCode.io pipelines dedicated to reachability analysis. long_description = file:README.rst long_description_content_type = text/x-rst -url = https://github.com/aboutcode-org/skeleton +url = https://github.com/aboutcode-org/reachablecode -author = nexB. Inc. and others +author = nexB. Inc., AboutCode and others author_email = info@aboutcode.org classifiers = @@ -31,15 +31,18 @@ license_files = README.rst [options] -python_requires = >=3.10 - package_dir = =src packages = find: include_package_data = true zip_safe = false +setup_requires = setuptools_scm[toml] >= 4 + +python_requires = >=3.8 + install_requires = + commoncode [options.packages.find] @@ -47,12 +50,18 @@ where = src [options.extras_require] -dev = - pytest >= 7.0.1 +testing = + pytest >= 6, != 7.0.0 pytest-xdist >= 2 aboutcode-toolkit >= 7.0.2 + pycodestyle >= 2.8.0 twine - ruff + black + isort + scancodeio + pytest-django + +docs = Sphinx>=5.0.2 sphinx-rtd-theme>=1.0.0 sphinx-reredirects >= 0.1.2 @@ -60,3 +69,8 @@ dev = sphinx-autobuild sphinx-rtd-dark-mode>=1.3.0 sphinx-copybutton + + +[options.entry_points] +scancodeio_pipelines = + analyze_atom_reachability = reachablecode.pipelines.analyze_atom_reachability:AtomReachability \ No newline at end of file diff --git a/src/README.rst b/src/README.rst deleted file mode 100644 index ec651fc..0000000 --- a/src/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -Put your Python source code (and installable data) in this directory. - diff --git a/src/reachablecode/__init__.py b/src/reachablecode/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/reachablecode/pipelines/__init__.py b/src/reachablecode/pipelines/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/reachablecode/pipelines/analyze_atom_reachability.py b/src/reachablecode/pipelines/analyze_atom_reachability.py new file mode 100644 index 0000000..aacfc9e --- /dev/null +++ b/src/reachablecode/pipelines/analyze_atom_reachability.py @@ -0,0 +1,92 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# http://nexb.com and https://github.com/aboutcode-org/scancode.io +# The ScanCode.io software is licensed under the Apache License version 2.0. +# Data generated with ScanCode.io is provided as-is without warranties. +# ScanCode is a trademark of nexB Inc. +# +# You may not use this software except in compliance with the License. +# You may obtain a copy of the License at: http://apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software distributed +# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# +# Data Generated with ScanCode.io is provided on an "AS IS" BASIS, WITHOUT WARRANTIES +# OR CONDITIONS OF ANY KIND, either express or implied. No content created from +# ScanCode.io should be considered or used as legal advice. Consult an Attorney +# for any legal advice. +# +# ScanCode.io is a free software code scanning tool from nexB Inc. and others. +# Visit https://github.com/aboutcode-org/scancode.io for support and download. + +from scanpipe.pipes.reachability_tools import ReachabilityPipeline + +from reachablecode.pipes import atom + + +class AtomReachability(ReachabilityPipeline): + """ + Determine the reachability of vulnerabilities using the atom tool. + + Note: You must run the ``find_vulnerabilities`` pipeline before running + this pipeline. + + atom is run twice against the project codebase: + + - ``usages`` writes a usages slice of the code. + - ``reachables`` writes a reachables slice of the symbols + that can actually be reached. + + The vulnerable and fixed symbols of each patch are matched against those + slices: + + - REACHABLE: the symbol has a call site in the code + - UNKNOWN: the symbol is defined in the code + - NOT_REACHABLE: the symbol is absent + + Results are stored in the ``extra_data`` of the matching resources under + the ``symbols_reachability`` key. + """ + + reachability_tool = atom.AtomTool + + @classmethod + def get_availability(cls): + return cls.reachability_tool.get_availability() + + def get_candidate_resources(self): + """Collect candidate resources for this pipeline.""" + self.candidate_resources = self.project.codebaseresources.files().filter( + is_binary=False, + is_archive=False, + is_media=False, + programming_language__in=list(self.reachability_tool.supported_languages), + ) + + def collect_resource_index(self): + """Run the atom usages and reachables commands and load both slices.""" + self.resource_indexe_paths = self.reachability_tool.run( + project=self.project, logger=self.log + ) + indexes = self.reachability_tool.parsed_output( + export_output_path=self.resource_indexe_paths, logger=self.log + ) + self.resource_indexes_usages = indexes.get("usages") or {} + self.resource_indexes_reachability = indexes.get("reachables") or {} + + def collect_and_match_resources(self): + """Match patch symbols against the atom slices.""" + if not self.resource_indexes_usages and not self.resource_indexes_reachability: + self.log("No atom usages or reachables slices available for matching.") + return + + atom.collect_and_match_resources( + tool_name=self.reachability_tool.tool_name, + patches=self.patches, + patch_symbols=self.patch_symbols, + usages_slices=self.resource_indexes_usages, + reachables_slices=self.resource_indexes_reachability, + candidate_resources=self.candidate_resources, + logger=self.log, + ) \ No newline at end of file diff --git a/src/reachablecode/pipes/__init__.py b/src/reachablecode/pipes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/reachablecode/pipes/atom.py b/src/reachablecode/pipes/atom.py new file mode 100644 index 0000000..1093724 --- /dev/null +++ b/src/reachablecode/pipes/atom.py @@ -0,0 +1,560 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# http://nexb.com and https://github.com/aboutcode-org/scancode.io +# The ScanCode.io software is licensed under the Apache License version 2.0. +# Data generated with ScanCode.io is provided as-is without warranties. +# ScanCode is a trademark of nexB Inc. +# +# You may not use this software except in compliance with the License. +# You may obtain a copy of the License at: http://apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software distributed +# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# +# Data Generated with ScanCode.io is provided on an "AS IS" BASIS, WITHOUT WARRANTIES +# OR CONDITIONS OF ANY KIND, either express or implied. No content created from +# ScanCode.io should be considered or used as legal advice. Consult an Attorney +# for any legal advice. +# +# ScanCode.io is a free software code scanning tool from nexB Inc. and others. +# Visit https://github.com/aboutcode-org/scancode.io for support and download. + +import json +import re +from collections import defaultdict +from os import environ +from subprocess import SubprocessError + +from aboutcode.pipeline import LoopProgress +from scanpipe.pipes import run_command_safely +from scanpipe.pipes.reachability import ReachabilityStatus, save_resource_reachability_report +from scanpipe.pipes.reachability_tools import ReachabilityTool + +ATOM_EXECUTABLE = environ.get("ATOM_EXECUTABLE") + +_OPERATOR_PREFIXES = ("", "") +_MARKER_PATTERN = re.compile(r"<[^>]+>") + + +def normalize_file_path(path): + """Return ``path`` as a normalized forward-slash relative path.""" + path = (path or "").replace("\\", "/") + while path.startswith("./"): + path = path[2:] + return path.lstrip("/") + + +def normalize_symbol_name(name): + """Convert an atom name to a comparable dotted form, or ``""`` if unusable.""" + if not isinstance(name, str): + return "" + name = name.strip() + if not name or name.startswith(_OPERATOR_PREFIXES): + return "" + + if "::" in name: + name = name.partition("::")[2] + + if ":" in name: + # Convert ``pkg/mod.py:.Class.method`` to ``pkg.mod.Class.method``. + path, _, tail = name.partition(":") + path = normalize_file_path(path).removesuffix(".py") + name = path.replace("/", ".") + "." + tail + + name = _MARKER_PATTERN.sub("", name) + + while ".." in name: + name = name.replace("..", ".") + + return name.strip(".") + + +class SliceMatcher: + """Index the dotted symbol names of an atom slice, grouped by file.""" + + def __init__(self, slices): + self.by_file = defaultdict(set) + self.by_basename = defaultdict(set) + if slices: + self.parse_slice(slices) + + @property + def file_count(self): + """Return the number of files indexed in this slice.""" + return len(self.by_file) + + def parse_slice(self, slices): + """Populate the indexes from ``slices``. Subclasses must implement this.""" + raise NotImplementedError + + def add_symbols(self, file_name, *names): + """Index each symbol in ``names`` under ``file_name``, in dotted form.""" + if not isinstance(file_name, str) or not file_name: + return + + path = normalize_file_path(file_name) + basename = path.rsplit("/", 1)[-1] + for name in names: + dotted = normalize_symbol_name(name) + if dotted: + self.by_file[path].add(dotted) + self.by_basename[basename].add(dotted) + + def match_symbols(self, symbols, file_name): + """Return the subset of ``symbols`` present in the slice for ``file_name``.""" + if not symbols: + return set() + + path = normalize_file_path(file_name) + names = self.by_file.get(path) or self.by_basename.get(path.rsplit("/", 1)[-1]) + if not names: + return set() + + matched = set() + for symbol in symbols: + dotted = normalize_symbol_name(symbol) + if not dotted: + continue + + if any( + name == dotted + or name.endswith("." + dotted) + or ("." in name and dotted.endswith("." + name)) + for name in names + ): + matched.add(symbol) + + return matched + + +class UsagesMatcher(SliceMatcher): + """Index the usages slice (objectSlices / userDefinedTypes) and its calls.""" + + def __init__(self, slices): + self.calls_by_file = defaultdict(dict) + self.calls_by_basename = defaultdict(dict) + super().__init__(slices) + + def parse_slice(self, slices): + if not isinstance(slices, dict): + return + + for obj in slices.get("objectSlices") or []: + self.add_object_slice(obj or {}) + + for typ in slices.get("userDefinedTypes") or []: + self.add_user_defined_type(typ or {}) + + def add_calls(self, file_name, symbols, calls): + """Associate ``calls`` with each symbol in ``symbols`` for ``file_name``.""" + if not isinstance(file_name, str) or not file_name or not calls: + return + + path = normalize_file_path(file_name) + basename = path.rsplit("/", 1)[-1] + for symbol in symbols: + dotted = normalize_symbol_name(symbol) + if not dotted: + continue + + file_slot = self.calls_by_file[path].setdefault(dotted, []) + base_slot = self.calls_by_basename[basename].setdefault(dotted, []) + + for call in calls: + if call not in file_slot: + file_slot.append(call) + if call not in base_slot: + base_slot.append(call) + + def get_calls(self, symbols, file_name): + """ + Return the calls associated with ``symbols`` in ``file_name``. + + Uses the same fuzzy matching as ``match_symbols`` (exact, suffix, or + prefix), deduplicated by ``(call_name, resolved_method, line_number)``. + """ + if not symbols: + return [] + + path = normalize_file_path(file_name) + file_calls = self.calls_by_file.get(path) + if not file_calls: + file_calls = self.calls_by_basename.get(path.rsplit("/", 1)[-1], {}) + if not file_calls: + return [] + + result = [] + seen = set() + for symbol in symbols: + dotted = normalize_symbol_name(symbol) + if not dotted: + continue + + for name, name_calls in file_calls.items(): + if not ( + name == dotted + or name.endswith("." + dotted) + or ("." in name and dotted.endswith("." + name)) + ): + continue + + for call in name_calls: + key = ( + call.get("call_name"), + call.get("resolved_method"), + call.get("line_number"), + ) + if key not in seen: + seen.add(key) + result.append(call) + return result + + def build_call_record(self, call): + """Return a compact call record from an atom call entry, or ``None``.""" + call = call or {} + call_name = call.get("callName") + resolved = call.get("resolvedMethod") + if not (call_name or resolved): + return None + + record = {} + if call_name: + record["call_name"] = call_name + + if resolved: + record["resolved_method"] = resolved + + if call.get("lineNumber") is not None: + record["line_number"] = call.get("lineNumber") + + if call.get("columnNumber") is not None: + record["column_number"] = call.get("columnNumber") + + return record + + def add_object_slice(self, obj): + """Index the symbols and calls of one ``objectSlices`` entry.""" + file_name = obj.get("fileName") + self.add_symbols(file_name, obj.get("fullName")) + + for usage in obj.get("usages") or []: + usage = usage or {} + + calls = [] + for raw_call in (usage.get("argToCalls") or []) + ( + usage.get("invokedCalls") or [] + ): + raw_call = raw_call or {} + self.add_symbols( + file_name, + raw_call.get("callName"), + raw_call.get("resolvedMethod"), + ) + record = self.build_call_record(raw_call) + if record: + calls.append(record) + + usage_symbols = [] + for part in (usage.get("targetObj"), usage.get("definedBy")): + part = part or {} + names = ( + part.get("name"), + part.get("typeFullName"), + part.get("resolvedMethod"), + ) + self.add_symbols(file_name, *names) + usage_symbols.extend(name for name in names if name) + + if usage_symbols and calls: + self.add_calls(file_name, usage_symbols, calls) + + def add_user_defined_type(self, typ): + """Index the symbols of one ``userDefinedTypes`` entry.""" + file_name = typ.get("fileName") + self.add_symbols(file_name, typ.get("name")) + + for field in typ.get("fields") or []: + field = field or {} + self.add_symbols( + file_name, + field.get("name"), + field.get("typeFullName"), + field.get("resolvedMethod"), + ) + + for proc in typ.get("procedures") or []: + proc = proc or {} + self.add_symbols( + file_name, proc.get("callName"), proc.get("resolvedMethod") + ) + + +class ReachablesMatcher(SliceMatcher): + """Index the ``reachables`` slice (``{"reachables": [{flows, purls}]}``).""" + + def parse_slice(self, slices): + if not isinstance(slices, list): + return + + for entry in slices: + for flow in (entry or {}).get("flows") or []: + flow = flow or {} + self.add_symbols( + flow.get("parentFileName"), + flow.get("name"), + flow.get("fullName"), + flow.get("typeFullName"), + flow.get("parentMethodName"), + flow.get("parentClassName"), + self.compose_class_method_name(flow), + ) + + def compose_class_method_name(self, flow): + """Return ``Class.method`` from a reachables flow, or ``""``.""" + method = flow.get("parentMethodName") or "" + klass = flow.get("parentClassName") or "" + + if not method or not klass: + return "" + + if ":" in klass: + klass = klass.partition(":")[2] + + return f"{klass}.{method}" + + +def classify_reachability(in_code, reachable): + """ + Return the reachability verdict for a set of vulnerable symbols. + + reachable is the subset matched against the reachables slice. + in_code is the subset matched against the usages slice. + """ + if reachable: + return ReachabilityStatus.REACHABLE + + if in_code: + return ReachabilityStatus.UNKNOWN + + return ReachabilityStatus.NOT_REACHABLE + + +def collect_and_match_resources( + tool_name, + patches, + patch_symbols, + usages_slices, + reachables_slices, + candidate_resources, + logger=None, +): + """ + Match patch symbols against the atom slices and store a reachability report + on each matching candidate resource. + + - a vulnerable symbol in ``reachables`` → REACHABLE + - a vulnerable symbol only in ``usages`` → UNKNOWN + - a vulnerable symbol in neither → NOT_REACHABLE + """ + usages = UsagesMatcher(usages_slices) + reachables = ReachablesMatcher(reachables_slices) + + if logger: + logger( + f"atom match: {usages.file_count} files in usages, " + f"{reachables.file_count} files in reachables, " + f"{len(candidate_resources)} candidates, {len(patches)} patches" + ) + + progress = LoopProgress(len(patches), logger=logger) + for patch in progress.iter(patches): + vcs_url = patch.get("vcs_url") + commit_hash = patch.get("commit_hash") + advisory_uids = patch.get("advisory_uids") or [] + + patch_symbols_by_language = patch_symbols.get(commit_hash, {}) + if not patch_symbols_by_language: + continue + + for resource in candidate_resources: + lang_patch_symbols = patch_symbols_by_language.get( + resource.programming_language + ) + if not lang_patch_symbols: + continue + + vulnerable = lang_patch_symbols.get("vulnerable") or {} + fixed = lang_patch_symbols.get("fixed") or {} + + path = resource.path + vuln_in_code = usages.match_symbols(vulnerable, path) + vuln_reachable = reachables.match_symbols(vulnerable, path) + fixed_in_code = usages.match_symbols(fixed, path) + + if not (vuln_in_code or vuln_reachable or fixed_in_code): + continue + + reachability_status = classify_reachability(vuln_in_code, vuln_reachable) + tool_details = [] + for symbol in sorted(vuln_in_code | vuln_reachable): + tool_details.append( + { + "symbol_name": symbol, + "in_usage": symbol in vuln_in_code, + "calls": usages.get_calls([symbol], path), + "in_reachable": symbol in vuln_reachable, + } + ) + + report = { + "tool_name": tool_name, + "patch": { + "vcs_url": vcs_url, + "commit_hash": commit_hash, + }, + "advisory_uids": advisory_uids, + "tool_details": tool_details, + "fixed_symbols": sorted(fixed), + "vulnerable_symbols": sorted(vulnerable), + "is_reachable": reachability_status.value, + } + + save_resource_reachability_report( + resource=resource, + commit_hash=commit_hash, + vcs_url=vcs_url, + new_report=report, + ) + + +class AtomTool(ReachabilityTool): + tool_name = "atom" + executable = ATOM_EXECUTABLE + supported_languages = { + "Java": "java", + "Python": "python", + } + + commands = ("usages", "reachables") + + @classmethod + def get_availability(cls): + if not cls.executable: + return ( + "atom is not configured. Set the ATOM_EXECUTABLE environment variable." + ) + + @classmethod + def get_atom_language(cls, project): + """Return the atom ``-l`` value for ``project`` from the scan summary.""" + summary_path = project.get_latest_output("summary") + with open(summary_path) as f: + summary = json.load(f) + + primary_language = summary.get("primary_language") + if not primary_language: + raise ValueError( + f"No primary_language in the scan summary of {project.name!r}." + ) + + language = cls.supported_languages.get(primary_language) + if not language: + raise ValueError( + f"Unsupported primary_language {primary_language!r} for " + f"{project.name!r}. Supported: {', '.join(cls.supported_languages)}." + ) + return language + + @staticmethod + def get_output_paths(project, atom_language): + """Return the output paths shared by the atom commands.""" + manifest_path = project.get_output_file_path("atom_reachability", "json") + prefix = f"{manifest_path.stem}.{atom_language}" + return { + "atom": manifest_path.with_name(f"{prefix}.atom"), + "usages": manifest_path.with_name(f"{prefix}.usages.slices.json"), + "reachables": manifest_path.with_name(f"{prefix}.reachables.slices.json"), + } + + @classmethod + def run_command( + cls, command, target_path, atom_language, output_paths, logger=None + ): + """Run a single atom command and return its slices file path or None.""" + command_args = [ + cls.executable, + command, + "-l", + atom_language, + "--cache", + "none", + "-o", + str(output_paths["atom"]), + "-s", + str(output_paths[command]), + str(target_path), + ] + + if logger: + logger(f"Running atom {command} on {target_path} ...") + try: + run_command_safely(command_args=command_args) + except SubprocessError as error: + if logger: + logger(f"atom {command} failed: {error!r}") + return None + return output_paths[command] + + @classmethod + def run(cls, project, logger=None): + """ + Run the atom ``usages`` and ``reachables`` commands. + + Return a mapping of command name to its slices file path (or ``None`` + when that command failed). + """ + target_path = project.codebase_path + if not target_path: + raise ValueError( + f"Cannot run Atom tools against project {project.name!r}:" + f" codebase_path is None." + ) + atom_language = cls.get_atom_language(project) + output_paths = cls.get_output_paths( + project=project, atom_language=atom_language + ) + + results = {} + for command in cls.commands: + results[command] = cls.run_command( + command=command, + target_path=target_path, + atom_language=atom_language, + output_paths=output_paths, + logger=logger, + ) + return results + + @staticmethod + def load_slices(export_output_path, logger=None): + """Load and return a single atom slices file.""" + if not export_output_path: + return {} + try: + with open(export_output_path) as slice_file: + return json.load(slice_file) + except (FileNotFoundError, json.JSONDecodeError) as error: + if logger: + logger(f"Skipping atom slices {export_output_path}: {error!r}") + return {} + + @classmethod + def parsed_output(cls, export_output_path, logger=None): + """Load and return the atom slices keyed by command name.""" + if not export_output_path: + return {} + return { + command: cls.load_slices(export_output_path=path, logger=logger) + for command, path in export_output_path.items() + } \ No newline at end of file diff --git a/tests/atom_reachability/python/app.py b/tests/atom_reachability/python/app.py new file mode 100644 index 0000000..074c86a --- /dev/null +++ b/tests/atom_reachability/python/app.py @@ -0,0 +1,40 @@ +import os + +from flask import Flask +from flask import request + +app = Flask(__name__) + +debug = False + + +class ReportGenerator: + """A dummy class to test AST class method parsing.""" + + def __init__(self, base_dir): + self.base_dir = base_dir + + +@app.route("/report") +def handle_request(): + """HTTP entry point so atom tags a framework-route source.""" + generator = ReportGenerator("/var/reports") + requested_file = request.args.get("file") + + # Helper function nested inside the route handler + def build_file_path(filename): + # VULNERABLE: Direct concatenation allows Path Traversal + return os.path.join(generator.base_dir, filename) + + if not requested_file: + return "Error: No file specified" + + target_path = build_file_path(requested_file) + + with open(target_path) as f: + return f.read() + + +def unrelated_top_level_function(): + """Test AST node boundaries.""" + return "I am just here to add AST complexity." diff --git a/tests/atom_reachability/python/atom_reachability.python.reachables.slices.json b/tests/atom_reachability/python/atom_reachability.python.reachables.slices.json new file mode 100644 index 0000000..bcf2464 --- /dev/null +++ b/tests/atom_reachability/python/atom_reachability.python.reachables.slices.json @@ -0,0 +1,1418 @@ +[ + { + "flows": [ + { + "id": 147, + "label": "IDENTIFIER", + "name": "tmp0", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "tmp0 = request.args", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 21, + "columnNumber": 22, + "tags": "framework-input" + }, + { + "id": 153, + "label": "CALL", + "name": "get", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "tmp0.get(\"file\")", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 21, + "columnNumber": 22, + "tags": "" + }, + { + "id": 154, + "label": "BLOCK", + "name": "", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "tmp0.get(\"file\")", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 21, + "columnNumber": 22, + "tags": "flow-summary" + }, + { + "id": 184, + "label": "IDENTIFIER", + "name": "requested_file", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "not requested_file", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 28, + "columnNumber": 12, + "tags": "" + }, + { + "id": 190, + "label": "IDENTIFIER", + "name": "requested_file", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "build_file_path(requested_file)", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 31, + "columnNumber": 35, + "tags": "" + }, + { + "id": 162, + "label": "METHOD_PARAMETER_IN", + "name": "filename", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "filename", + "typeFullName": "ANY", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 24, + "columnNumber": 25, + "tags": "flow-summary" + }, + { + "id": 167, + "label": "IDENTIFIER", + "name": "filename", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "tmp1.join(generator.base_dir, filename)", + "typeFullName": "", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 26, + "columnNumber": 49, + "tags": "" + }, + { + "id": 177, + "label": "CALL", + "name": "join", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "tmp1.join(generator.base_dir, filename)", + "typeFullName": "", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 26, + "columnNumber": 16, + "tags": "" + }, + { + "id": 178, + "label": "BLOCK", + "name": "", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "tmp1.join(generator.base_dir, filename)", + "typeFullName": "", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 26, + "columnNumber": 16, + "tags": "flow-summary" + }, + { + "id": 179, + "label": "RETURN", + "name": "", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "return os.path.join(generator.base_dir, filename)", + "typeFullName": "", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 26, + "columnNumber": 9, + "tags": "" + }, + { + "id": 163, + "label": "METHOD_RETURN", + "name": "", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "RET", + "typeFullName": "", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 24, + "columnNumber": 5, + "tags": "flow-summary" + }, + { + "id": 192, + "label": "CALL", + "name": "build_file_path", + "fullName": "app.py:.handle_request.build_file_path", + "signature": "", + "isExternal": false, + "code": "build_file_path(requested_file)", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 31, + "columnNumber": 19, + "tags": "" + }, + { + "id": 201, + "label": "IDENTIFIER", + "name": "target_path", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "open(target_path)", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 33, + "columnNumber": 15, + "tags": "file-io" + }, + { + "id": 203, + "label": "CALL", + "name": "open", + "fullName": "__builtin.open", + "signature": "", + "isExternal": true, + "code": "open(target_path)", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 33, + "columnNumber": 10, + "tags": "file-io" + } + ], + "purls": [] + }, + { + "flows": [ + { + "id": 155, + "label": "IDENTIFIER", + "name": "requested_file", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "requested_file = tmp0 = request.args\ntmp0.get(\"file\")", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 21, + "columnNumber": 5, + "tags": "framework-input" + }, + { + "id": 184, + "label": "IDENTIFIER", + "name": "requested_file", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "not requested_file", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 28, + "columnNumber": 12, + "tags": "" + }, + { + "id": 190, + "label": "IDENTIFIER", + "name": "requested_file", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "build_file_path(requested_file)", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 31, + "columnNumber": 35, + "tags": "" + }, + { + "id": 162, + "label": "METHOD_PARAMETER_IN", + "name": "filename", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "filename", + "typeFullName": "ANY", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 24, + "columnNumber": 25, + "tags": "flow-summary" + }, + { + "id": 167, + "label": "IDENTIFIER", + "name": "filename", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "tmp1.join(generator.base_dir, filename)", + "typeFullName": "", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 26, + "columnNumber": 49, + "tags": "" + }, + { + "id": 177, + "label": "CALL", + "name": "join", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "tmp1.join(generator.base_dir, filename)", + "typeFullName": "", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 26, + "columnNumber": 16, + "tags": "" + }, + { + "id": 178, + "label": "BLOCK", + "name": "", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "tmp1.join(generator.base_dir, filename)", + "typeFullName": "", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 26, + "columnNumber": 16, + "tags": "flow-summary" + }, + { + "id": 179, + "label": "RETURN", + "name": "", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "return os.path.join(generator.base_dir, filename)", + "typeFullName": "", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 26, + "columnNumber": 9, + "tags": "" + }, + { + "id": 163, + "label": "METHOD_RETURN", + "name": "", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "RET", + "typeFullName": "", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 24, + "columnNumber": 5, + "tags": "flow-summary" + }, + { + "id": 192, + "label": "CALL", + "name": "build_file_path", + "fullName": "app.py:.handle_request.build_file_path", + "signature": "", + "isExternal": false, + "code": "build_file_path(requested_file)", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 31, + "columnNumber": 19, + "tags": "" + }, + { + "id": 201, + "label": "IDENTIFIER", + "name": "target_path", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "open(target_path)", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 33, + "columnNumber": 15, + "tags": "file-io" + }, + { + "id": 203, + "label": "CALL", + "name": "open", + "fullName": "__builtin.open", + "signature": "", + "isExternal": true, + "code": "open(target_path)", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 33, + "columnNumber": 10, + "tags": "file-io" + } + ], + "purls": [] + }, + { + "flows": [ + { + "id": 147, + "label": "IDENTIFIER", + "name": "tmp0", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "tmp0 = request.args", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 21, + "columnNumber": 22, + "tags": "framework-input" + }, + { + "id": 153, + "label": "CALL", + "name": "get", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "tmp0.get(\"file\")", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 21, + "columnNumber": 22, + "tags": "" + }, + { + "id": 154, + "label": "BLOCK", + "name": "", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "tmp0.get(\"file\")", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 21, + "columnNumber": 22, + "tags": "flow-summary" + }, + { + "id": 184, + "label": "IDENTIFIER", + "name": "requested_file", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "not requested_file", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 28, + "columnNumber": 12, + "tags": "" + }, + { + "id": 190, + "label": "IDENTIFIER", + "name": "requested_file", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "build_file_path(requested_file)", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 31, + "columnNumber": 35, + "tags": "" + }, + { + "id": 162, + "label": "METHOD_PARAMETER_IN", + "name": "filename", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "filename", + "typeFullName": "ANY", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 24, + "columnNumber": 25, + "tags": "flow-summary" + }, + { + "id": 167, + "label": "IDENTIFIER", + "name": "filename", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "tmp1.join(generator.base_dir, filename)", + "typeFullName": "", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 26, + "columnNumber": 49, + "tags": "" + }, + { + "id": 177, + "label": "CALL", + "name": "join", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "tmp1.join(generator.base_dir, filename)", + "typeFullName": "", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 26, + "columnNumber": 16, + "tags": "" + }, + { + "id": 178, + "label": "BLOCK", + "name": "", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "tmp1.join(generator.base_dir, filename)", + "typeFullName": "", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 26, + "columnNumber": 16, + "tags": "flow-summary" + }, + { + "id": 179, + "label": "RETURN", + "name": "", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "return os.path.join(generator.base_dir, filename)", + "typeFullName": "", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 26, + "columnNumber": 9, + "tags": "" + }, + { + "id": 163, + "label": "METHOD_RETURN", + "name": "", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "RET", + "typeFullName": "", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 24, + "columnNumber": 5, + "tags": "flow-summary" + }, + { + "id": 192, + "label": "CALL", + "name": "build_file_path", + "fullName": "app.py:.handle_request.build_file_path", + "signature": "", + "isExternal": false, + "code": "build_file_path(requested_file)", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 31, + "columnNumber": 19, + "tags": "" + }, + { + "id": 201, + "label": "IDENTIFIER", + "name": "target_path", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "open(target_path)", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 33, + "columnNumber": 15, + "tags": "file-io" + }, + { + "id": 203, + "label": "CALL", + "name": "open", + "fullName": "__builtin.open", + "signature": "", + "isExternal": true, + "code": "open(target_path)", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 33, + "columnNumber": 10, + "tags": "file-io" + } + ], + "purls": [] + }, + { + "flows": [ + { + "id": 147, + "label": "IDENTIFIER", + "name": "tmp0", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "tmp0 = request.args", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 21, + "columnNumber": 22, + "tags": "framework-input" + }, + { + "id": 153, + "label": "CALL", + "name": "get", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "tmp0.get(\"file\")", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 21, + "columnNumber": 22, + "tags": "" + }, + { + "id": 154, + "label": "BLOCK", + "name": "", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "tmp0.get(\"file\")", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 21, + "columnNumber": 22, + "tags": "flow-summary" + }, + { + "id": 184, + "label": "IDENTIFIER", + "name": "requested_file", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "not requested_file", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 28, + "columnNumber": 12, + "tags": "" + }, + { + "id": 190, + "label": "IDENTIFIER", + "name": "requested_file", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "build_file_path(requested_file)", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 31, + "columnNumber": 35, + "tags": "" + }, + { + "id": 162, + "label": "METHOD_PARAMETER_IN", + "name": "filename", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "filename", + "typeFullName": "ANY", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 24, + "columnNumber": 25, + "tags": "flow-summary" + }, + { + "id": 167, + "label": "IDENTIFIER", + "name": "filename", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "tmp1.join(generator.base_dir, filename)", + "typeFullName": "", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 26, + "columnNumber": 49, + "tags": "" + }, + { + "id": 177, + "label": "CALL", + "name": "join", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "tmp1.join(generator.base_dir, filename)", + "typeFullName": "", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 26, + "columnNumber": 16, + "tags": "" + }, + { + "id": 178, + "label": "BLOCK", + "name": "", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "tmp1.join(generator.base_dir, filename)", + "typeFullName": "", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 26, + "columnNumber": 16, + "tags": "flow-summary" + }, + { + "id": 179, + "label": "RETURN", + "name": "", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "return os.path.join(generator.base_dir, filename)", + "typeFullName": "", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 26, + "columnNumber": 9, + "tags": "" + }, + { + "id": 163, + "label": "METHOD_RETURN", + "name": "", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "RET", + "typeFullName": "", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 24, + "columnNumber": 5, + "tags": "flow-summary" + }, + { + "id": 192, + "label": "CALL", + "name": "build_file_path", + "fullName": "app.py:.handle_request.build_file_path", + "signature": "", + "isExternal": false, + "code": "build_file_path(requested_file)", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 31, + "columnNumber": 19, + "tags": "" + }, + { + "id": 201, + "label": "IDENTIFIER", + "name": "target_path", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "open(target_path)", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 33, + "columnNumber": 15, + "tags": "file-io" + } + ], + "purls": [] + }, + { + "flows": [ + { + "id": 147, + "label": "IDENTIFIER", + "name": "tmp0", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "tmp0 = request.args", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 21, + "columnNumber": 22, + "tags": "framework-input" + }, + { + "id": 153, + "label": "CALL", + "name": "get", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "tmp0.get(\"file\")", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 21, + "columnNumber": 22, + "tags": "" + }, + { + "id": 154, + "label": "BLOCK", + "name": "", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "tmp0.get(\"file\")", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 21, + "columnNumber": 22, + "tags": "flow-summary" + }, + { + "id": 184, + "label": "IDENTIFIER", + "name": "requested_file", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "not requested_file", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 28, + "columnNumber": 12, + "tags": "" + }, + { + "id": 190, + "label": "IDENTIFIER", + "name": "requested_file", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "build_file_path(requested_file)", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 31, + "columnNumber": 35, + "tags": "" + }, + { + "id": 162, + "label": "METHOD_PARAMETER_IN", + "name": "filename", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "filename", + "typeFullName": "ANY", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 24, + "columnNumber": 25, + "tags": "flow-summary" + }, + { + "id": 167, + "label": "IDENTIFIER", + "name": "filename", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "tmp1.join(generator.base_dir, filename)", + "typeFullName": "", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 26, + "columnNumber": 49, + "tags": "" + }, + { + "id": 177, + "label": "CALL", + "name": "join", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "tmp1.join(generator.base_dir, filename)", + "typeFullName": "", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 26, + "columnNumber": 16, + "tags": "" + }, + { + "id": 178, + "label": "BLOCK", + "name": "", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "tmp1.join(generator.base_dir, filename)", + "typeFullName": "", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 26, + "columnNumber": 16, + "tags": "flow-summary" + }, + { + "id": 179, + "label": "RETURN", + "name": "", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "return os.path.join(generator.base_dir, filename)", + "typeFullName": "", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 26, + "columnNumber": 9, + "tags": "" + }, + { + "id": 163, + "label": "METHOD_RETURN", + "name": "", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "RET", + "typeFullName": "", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 24, + "columnNumber": 5, + "tags": "flow-summary" + }, + { + "id": 192, + "label": "CALL", + "name": "build_file_path", + "fullName": "app.py:.handle_request.build_file_path", + "signature": "", + "isExternal": false, + "code": "build_file_path(requested_file)", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 31, + "columnNumber": 19, + "tags": "" + }, + { + "id": 201, + "label": "IDENTIFIER", + "name": "target_path", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "open(target_path)", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 33, + "columnNumber": 15, + "tags": "file-io" + } + ], + "purls": [] + }, + { + "flows": [ + { + "id": 155, + "label": "IDENTIFIER", + "name": "requested_file", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "requested_file = tmp0 = request.args\ntmp0.get(\"file\")", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 21, + "columnNumber": 5, + "tags": "framework-input" + }, + { + "id": 184, + "label": "IDENTIFIER", + "name": "requested_file", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "not requested_file", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 28, + "columnNumber": 12, + "tags": "" + }, + { + "id": 190, + "label": "IDENTIFIER", + "name": "requested_file", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "build_file_path(requested_file)", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 31, + "columnNumber": 35, + "tags": "" + }, + { + "id": 162, + "label": "METHOD_PARAMETER_IN", + "name": "filename", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "filename", + "typeFullName": "ANY", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 24, + "columnNumber": 25, + "tags": "flow-summary" + }, + { + "id": 167, + "label": "IDENTIFIER", + "name": "filename", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "tmp1.join(generator.base_dir, filename)", + "typeFullName": "", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 26, + "columnNumber": 49, + "tags": "" + }, + { + "id": 177, + "label": "CALL", + "name": "join", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "tmp1.join(generator.base_dir, filename)", + "typeFullName": "", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 26, + "columnNumber": 16, + "tags": "" + }, + { + "id": 178, + "label": "BLOCK", + "name": "", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "tmp1.join(generator.base_dir, filename)", + "typeFullName": "", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 26, + "columnNumber": 16, + "tags": "flow-summary" + }, + { + "id": 179, + "label": "RETURN", + "name": "", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "return os.path.join(generator.base_dir, filename)", + "typeFullName": "", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 26, + "columnNumber": 9, + "tags": "" + }, + { + "id": 163, + "label": "METHOD_RETURN", + "name": "", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "RET", + "typeFullName": "", + "parentMethodName": "build_file_path", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 24, + "columnNumber": 5, + "tags": "flow-summary" + }, + { + "id": 192, + "label": "CALL", + "name": "build_file_path", + "fullName": "app.py:.handle_request.build_file_path", + "signature": "", + "isExternal": false, + "code": "build_file_path(requested_file)", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 31, + "columnNumber": 19, + "tags": "" + }, + { + "id": 201, + "label": "IDENTIFIER", + "name": "target_path", + "fullName": "", + "signature": "", + "isExternal": false, + "code": "open(target_path)", + "typeFullName": "", + "parentMethodName": "handle_request", + "parentMethodSignature": "", + "parentFileName": "app.py", + "parentPackageName": "", + "parentClassName": "", + "lineNumber": 33, + "columnNumber": 15, + "tags": "file-io" + } + ], + "purls": [] + } +] \ No newline at end of file diff --git a/tests/atom_reachability/python/atom_reachability.python.usages.slices.json b/tests/atom_reachability/python/atom_reachability.python.usages.slices.json new file mode 100644 index 0000000..d6ce7a0 --- /dev/null +++ b/tests/atom_reachability/python/atom_reachability.python.usages.slices.json @@ -0,0 +1,1534 @@ +{ + "objectSlices": [ + { + "code": "", + "fullName": "app.py:", + "signature": "", + "fileName": "app.py", + "lineNumber": 1, + "columnNumber": 1, + "usages": [ + { + "targetObj": { + "name": "", + "typeFullName": "ANY", + "resolvedMethod": "def ", + "isExternal": true, + "lineNumber": 10, + "columnNumber": 1, + "label": "CALL" + }, + "definedBy": { + "name": "", + "typeFullName": "ANY", + "resolvedMethod": "def ", + "isExternal": true, + "lineNumber": 10, + "columnNumber": 1, + "label": "CALL" + }, + "invokedCalls": [], + "argToCalls": [] + }, + { + "targetObj": { + "name": "app", + "typeFullName": "flask.py:.Flask", + "lineNumber": 5, + "columnNumber": 1, + "label": "LOCAL" + }, + "definedBy": { + "name": "Flask", + "typeFullName": "ANY", + "resolvedMethod": "flask.py:.Flask.__init__", + "isExternal": null, + "lineNumber": 5, + "columnNumber": 7, + "label": "CALL" + }, + "invokedCalls": [], + "argToCalls": [ + { + "callName": "route", + "resolvedMethod": "flask.py:.Flask.route", + "paramTypes": [ + "ANY" + ], + "returnType": "ANY", + "position": 0, + "isExternal": true, + "lineNumber": 17, + "columnNumber": 2 + } + ] + }, + { + "targetObj": { + "name": "route", + "typeFullName": "ANY", + "resolvedMethod": "flask.py:.Flask.route", + "isExternal": true, + "lineNumber": 17, + "columnNumber": 2, + "label": "CALL" + }, + "definedBy": { + "name": "route", + "typeFullName": "ANY", + "resolvedMethod": "flask.py:.Flask.route", + "isExternal": true, + "lineNumber": 17, + "columnNumber": 2, + "label": "CALL" + }, + "invokedCalls": [], + "argToCalls": [] + }, + { + "targetObj": { + "name": ".assignment", + "typeFullName": "ANY", + "resolvedMethod": ".assignment", + "isExternal": true, + "lineNumber": 37, + "columnNumber": 1, + "label": "CALL" + }, + "definedBy": { + "name": ".assignment", + "typeFullName": "ANY", + "resolvedMethod": ".assignment", + "isExternal": true, + "lineNumber": 37, + "columnNumber": 1, + "label": "CALL" + }, + "invokedCalls": [], + "argToCalls": [] + }, + { + "targetObj": { + "name": "", + "typeFullName": "ANY", + "resolvedMethod": "app.route", + "isExternal": true, + "lineNumber": 17, + "columnNumber": 2, + "label": "CALL" + }, + "definedBy": { + "name": "", + "typeFullName": "ANY", + "resolvedMethod": "app.route", + "isExternal": true, + "lineNumber": 17, + "columnNumber": 2, + "label": "CALL" + }, + "invokedCalls": [], + "argToCalls": [] + }, + { + "targetObj": { + "name": "", + "typeFullName": "ANY", + "resolvedMethod": "app.py:", + "isExternal": false, + "lineNumber": 1, + "columnNumber": 1, + "label": "CALL" + }, + "definedBy": { + "name": "", + "typeFullName": "ANY", + "resolvedMethod": "app.py:", + "isExternal": false, + "lineNumber": 1, + "columnNumber": 1, + "label": "CALL" + }, + "invokedCalls": [], + "argToCalls": [] + }, + { + "targetObj": { + "name": "Flask", + "typeFullName": "ANY", + "resolvedMethod": "flask.py:.Flask.__init__", + "isExternal": true, + "lineNumber": 5, + "columnNumber": 7, + "label": "CALL" + }, + "definedBy": { + "name": "Flask", + "typeFullName": "ANY", + "resolvedMethod": "flask.py:.Flask.__init__", + "isExternal": true, + "lineNumber": 5, + "columnNumber": 7, + "label": "CALL" + }, + "invokedCalls": [], + "argToCalls": [] + } + ] + }, + { + "code": "", + "fullName": "app.py:.ReportGenerator.", + "signature": "", + "fileName": "app.py", + "lineNumber": 10, + "columnNumber": 1, + "usages": [ + { + "targetObj": { + "name": "", + "typeFullName": "ANY", + "resolvedMethod": "app.py:.ReportGenerator.", + "isExternal": false, + "lineNumber": 10, + "columnNumber": 1, + "label": "CALL" + }, + "definedBy": { + "name": "", + "typeFullName": "ANY", + "resolvedMethod": "app.py:.ReportGenerator.", + "isExternal": false, + "lineNumber": 10, + "columnNumber": 1, + "label": "CALL" + }, + "invokedCalls": [], + "argToCalls": [] + } + ] + }, + { + "code": "", + "fullName": "app.py:.ReportGenerator.", + "signature": "", + "fileName": "app.py", + "lineNumber": 13, + "columnNumber": 18, + "usages": [ + { + "targetObj": { + "name": "base_dir", + "typeFullName": "ANY", + "position": 1, + "lineNumber": 13, + "columnNumber": 24, + "label": "PARAM" + }, + "definedBy": { + "name": "base_dir", + "typeFullName": "ANY", + "position": 1, + "lineNumber": 13, + "columnNumber": 24, + "label": "PARAM" + }, + "invokedCalls": [ + { + "callName": "__init__", + "resolvedMethod": "app.py:.ReportGenerator.__init__", + "paramTypes": [ + "ANY", + "ANY" + ], + "returnType": "ANY", + "isExternal": false, + "lineNumber": 13, + "columnNumber": 18 + } + ], + "argToCalls": [] + }, + { + "targetObj": { + "name": "", + "typeFullName": "ANY", + "resolvedMethod": "app.py:.ReportGenerator.", + "isExternal": false, + "lineNumber": 13, + "columnNumber": 18, + "label": "CALL" + }, + "definedBy": { + "name": "", + "typeFullName": "ANY", + "resolvedMethod": "app.py:.ReportGenerator.", + "isExternal": false, + "lineNumber": 13, + "columnNumber": 18, + "label": "CALL" + }, + "invokedCalls": [], + "argToCalls": [] + }, + { + "targetObj": { + "name": ".alloc", + "typeFullName": "ANY", + "resolvedMethod": ".alloc", + "isExternal": true, + "lineNumber": 13, + "columnNumber": 18, + "label": "CALL" + }, + "definedBy": { + "name": ".alloc", + "typeFullName": "ANY", + "resolvedMethod": ".alloc", + "isExternal": true, + "lineNumber": 13, + "columnNumber": 18, + "label": "CALL" + }, + "invokedCalls": [], + "argToCalls": [] + }, + { + "targetObj": { + "name": "__newInstance", + "typeFullName": "ANY", + "lineNumber": 13, + "columnNumber": 18, + "label": "LOCAL" + }, + "definedBy": { + "name": "", + "typeFullName": "ANY", + "resolvedMethod": ".alloc", + "isExternal": null, + "lineNumber": 13, + "columnNumber": 18, + "label": "CALL" + }, + "invokedCalls": [ + { + "callName": "__newInstance = ", + "resolvedMethod": null, + "paramTypes": [], + "returnType": "ANY", + "isExternal": true, + "lineNumber": 13, + "columnNumber": 18 + }, + { + "callName": "__init__", + "resolvedMethod": "app.py:.ReportGenerator.__init__", + "paramTypes": [ + "ANY", + "ANY" + ], + "returnType": "ANY", + "isExternal": false, + "lineNumber": 13, + "columnNumber": 18 + } + ], + "argToCalls": [] + }, + { + "targetObj": { + "name": "cls", + "typeFullName": "app.py:.ReportGenerator", + "position": 0, + "lineNumber": 13, + "columnNumber": 18, + "label": "PARAM" + }, + "definedBy": { + "name": "cls", + "typeFullName": "app.py:.ReportGenerator", + "position": 0, + "lineNumber": 13, + "columnNumber": 18, + "label": "PARAM" + }, + "invokedCalls": [], + "argToCalls": [ + { + "callName": "__init__", + "resolvedMethod": "app.py:.ReportGenerator.__init__", + "paramTypes": [ + "ANY", + "ANY" + ], + "returnType": "ANY", + "position": 0, + "isExternal": false, + "lineNumber": 13, + "columnNumber": 18 + } + ] + } + ] + }, + { + "code": "", + "fullName": "app.py:.ReportGenerator.", + "signature": "", + "fileName": "app.py", + "lineNumber": 13, + "columnNumber": 18, + "usages": [ + { + "targetObj": { + "name": "", + "typeFullName": "app.py:.ReportGenerator", + "resolvedMethod": "app.py:.ReportGenerator.", + "isExternal": false, + "lineNumber": 13, + "columnNumber": 18, + "label": "CALL" + }, + "definedBy": { + "name": "", + "typeFullName": "app.py:.ReportGenerator", + "resolvedMethod": "app.py:.ReportGenerator.", + "isExternal": false, + "lineNumber": 13, + "columnNumber": 18, + "label": "CALL" + }, + "invokedCalls": [], + "argToCalls": [] + }, + { + "targetObj": { + "name": "base_dir", + "typeFullName": "ANY", + "position": 1, + "lineNumber": 13, + "columnNumber": 24, + "label": "PARAM" + }, + "definedBy": { + "name": "base_dir", + "typeFullName": "ANY", + "position": 1, + "lineNumber": 13, + "columnNumber": 24, + "label": "PARAM" + }, + "invokedCalls": [ + { + "callName": "", + "resolvedMethod": "class ReportGenerator", + "paramTypes": [ + "ANY" + ], + "returnType": "ANY", + "isExternal": true, + "lineNumber": 13, + "columnNumber": 18 + } + ], + "argToCalls": [] + }, + { + "targetObj": { + "name": "", + "typeFullName": "ANY", + "resolvedMethod": "class ReportGenerator", + "isExternal": true, + "lineNumber": 13, + "columnNumber": 18, + "label": "CALL" + }, + "definedBy": { + "name": "", + "typeFullName": "ANY", + "resolvedMethod": "class ReportGenerator", + "isExternal": true, + "lineNumber": 13, + "columnNumber": 18, + "label": "CALL" + }, + "invokedCalls": [], + "argToCalls": [] + } + ] + }, + { + "code": "", + "fullName": "app.py:.ReportGenerator.__init__", + "signature": "", + "fileName": "app.py", + "lineNumber": 13, + "columnNumber": 5, + "usages": [ + { + "targetObj": { + "name": "ReportGenerator", + "typeFullName": "ANY", + "resolvedMethod": "app.py:.ReportGenerator.__init__", + "isExternal": false, + "lineNumber": 20, + "columnNumber": 17, + "label": "CALL" + }, + "definedBy": { + "name": "ReportGenerator", + "typeFullName": "ANY", + "resolvedMethod": "app.py:.ReportGenerator.__init__", + "isExternal": false, + "lineNumber": 20, + "columnNumber": 17, + "label": "CALL" + }, + "invokedCalls": [], + "argToCalls": [] + }, + { + "targetObj": { + "name": ".fieldAccess", + "typeFullName": "ANY", + "resolvedMethod": ".fieldAccess", + "isExternal": true, + "lineNumber": 17, + "columnNumber": 2, + "label": "CALL" + }, + "definedBy": { + "name": ".fieldAccess", + "typeFullName": "ANY", + "resolvedMethod": ".fieldAccess", + "isExternal": true, + "lineNumber": 17, + "columnNumber": 2, + "label": "CALL" + }, + "invokedCalls": [], + "argToCalls": [] + } + ] + }, + { + "code": "", + "fullName": "app.py:.ReportGenerator.__init__", + "signature": "", + "fileName": "app.py", + "lineNumber": 13, + "columnNumber": 5, + "usages": [ + { + "targetObj": { + "name": "__init__", + "typeFullName": "ANY", + "resolvedMethod": "app.py:.ReportGenerator.__init__", + "isExternal": false, + "lineNumber": 13, + "columnNumber": 5, + "label": "CALL" + }, + "definedBy": { + "name": "__init__", + "typeFullName": "ANY", + "resolvedMethod": "app.py:.ReportGenerator.__init__", + "isExternal": false, + "lineNumber": 13, + "columnNumber": 5, + "label": "CALL" + }, + "invokedCalls": [], + "argToCalls": [] + }, + { + "targetObj": { + "name": "base_dir", + "typeFullName": "ANY", + "position": 2, + "lineNumber": 13, + "columnNumber": 24, + "label": "PARAM" + }, + "definedBy": { + "name": "base_dir", + "typeFullName": "ANY", + "position": 2, + "lineNumber": 13, + "columnNumber": 24, + "label": "PARAM" + }, + "invokedCalls": [ + { + "callName": "app.py:.ReportGenerator.__init__", + "resolvedMethod": "app.py:.ReportGenerator.__init__", + "paramTypes": [ + "ANY" + ], + "returnType": "ANY", + "isExternal": false, + "lineNumber": 13, + "columnNumber": 5 + } + ], + "argToCalls": [] + } + ] + }, + { + "code": "", + "fullName": "app.py:.handle_request", + "signature": "", + "fileName": "app.py", + "lineNumber": 18, + "columnNumber": 1, + "usages": [ + { + "targetObj": { + "name": "manager_tmp2", + "typeFullName": "ANY", + "lineNumber": 33, + "columnNumber": 10, + "label": "LOCAL" + }, + "definedBy": { + "name": "open", + "typeFullName": "ANY", + "resolvedMethod": "__builtin.open", + "isExternal": null, + "lineNumber": 33, + "columnNumber": 10, + "label": "CALL" + }, + "invokedCalls": [], + "argToCalls": [ + { + "callName": "", + "resolvedMethod": null, + "paramTypes": [], + "returnType": "ANY", + "position": 0, + "isExternal": null, + "lineNumber": 33, + "columnNumber": 10 + }, + { + "callName": "", + "resolvedMethod": null, + "paramTypes": [], + "returnType": "ANY", + "position": 0, + "isExternal": null, + "lineNumber": 33, + "columnNumber": 10 + } + ] + }, + { + "targetObj": { + "name": "target_path", + "typeFullName": "ANY", + "lineNumber": 31, + "columnNumber": 5, + "label": "LOCAL" + }, + "definedBy": { + "name": "build_file_path", + "typeFullName": "ANY", + "resolvedMethod": "app.py:.handle_request.build_file_path", + "isExternal": null, + "lineNumber": 31, + "columnNumber": 19, + "label": "CALL" + }, + "invokedCalls": [ + { + "callName": "open", + "resolvedMethod": "__builtin.open", + "paramTypes": [ + "ANY" + ], + "returnType": "ANY", + "isExternal": true, + "lineNumber": 33, + "columnNumber": 10 + } + ], + "argToCalls": [] + }, + { + "targetObj": { + "name": "app.route", + "typeFullName": "app.route", + "resolvedMethod": "@app.route(\"/report\")", + "isExternal": false, + "lineNumber": 17, + "columnNumber": 2, + "label": "ANNOTATION" + }, + "definedBy": { + "name": "app.route", + "typeFullName": "app.route", + "resolvedMethod": "@app.route(\"/report\")", + "isExternal": false, + "lineNumber": 17, + "columnNumber": 2, + "label": "ANNOTATION" + }, + "invokedCalls": [ + { + "callName": "app.route", + "resolvedMethod": "@app.route(\"/report\")", + "paramTypes": [], + "returnType": "", + "isExternal": false, + "lineNumber": 17, + "columnNumber": 2 + } + ], + "argToCalls": [] + }, + { + "targetObj": { + "name": "tmp0", + "typeFullName": "args", + "lineNumber": 21, + "columnNumber": 22, + "label": "LOCAL" + }, + "definedBy": { + "name": "request.args", + "typeFullName": "ANY", + "resolvedMethod": ".fieldAccess", + "isExternal": null, + "lineNumber": 21, + "columnNumber": 22, + "label": "CALL" + }, + "invokedCalls": [], + "argToCalls": [ + { + "callName": "get", + "resolvedMethod": null, + "paramTypes": [ + "ANY" + ], + "returnType": "ANY", + "position": 0, + "isExternal": null, + "lineNumber": 21, + "columnNumber": 22 + } + ] + }, + { + "targetObj": { + "name": "read", + "typeFullName": "ANY", + "resolvedMethod": "f.read", + "isExternal": true, + "lineNumber": 34, + "columnNumber": 16, + "label": "CALL" + }, + "definedBy": { + "name": "read", + "typeFullName": "ANY", + "resolvedMethod": "f.read", + "isExternal": true, + "lineNumber": 34, + "columnNumber": 16, + "label": "CALL" + }, + "invokedCalls": [], + "argToCalls": [] + }, + { + "targetObj": { + "name": ".logicalNot", + "typeFullName": "ANY", + "resolvedMethod": ".logicalNot", + "isExternal": true, + "lineNumber": 28, + "columnNumber": 8, + "label": "CALL" + }, + "definedBy": { + "name": ".logicalNot", + "typeFullName": "ANY", + "resolvedMethod": ".logicalNot", + "isExternal": true, + "lineNumber": 28, + "columnNumber": 8, + "label": "CALL" + }, + "invokedCalls": [], + "argToCalls": [] + }, + { + "targetObj": { + "name": "open", + "typeFullName": "ANY", + "resolvedMethod": "__builtin.open", + "isExternal": true, + "lineNumber": 33, + "columnNumber": 10, + "label": "CALL" + }, + "definedBy": { + "name": "open", + "typeFullName": "ANY", + "resolvedMethod": "__builtin.open", + "isExternal": true, + "lineNumber": 33, + "columnNumber": 10, + "label": "CALL" + }, + "invokedCalls": [], + "argToCalls": [] + } + ] + }, + { + "code": "", + "fullName": "app.py:.handle_request.build_file_path", + "signature": "", + "fileName": "app.py", + "lineNumber": 24, + "columnNumber": 5, + "usages": [ + { + "targetObj": { + "name": "build_file_path", + "typeFullName": "ANY", + "resolvedMethod": "app.py:.handle_request.build_file_path", + "isExternal": false, + "lineNumber": 31, + "columnNumber": 19, + "label": "CALL" + }, + "definedBy": { + "name": "build_file_path", + "typeFullName": "ANY", + "resolvedMethod": "app.py:.handle_request.build_file_path", + "isExternal": false, + "lineNumber": 31, + "columnNumber": 19, + "label": "CALL" + }, + "invokedCalls": [], + "argToCalls": [] + }, + { + "targetObj": { + "name": "tmp1", + "typeFullName": "path", + "lineNumber": 26, + "columnNumber": 16, + "label": "LOCAL" + }, + "definedBy": { + "name": "os.path", + "typeFullName": "ANY", + "resolvedMethod": ".fieldAccess", + "isExternal": null, + "lineNumber": 26, + "columnNumber": 16, + "label": "CALL" + }, + "invokedCalls": [], + "argToCalls": [ + { + "callName": "join", + "resolvedMethod": null, + "paramTypes": [ + "ANY", + "ANY" + ], + "returnType": "ANY", + "position": 0, + "isExternal": null, + "lineNumber": 26, + "columnNumber": 16 + } + ] + }, + { + "targetObj": { + "name": "filename", + "typeFullName": "ANY", + "position": 1, + "lineNumber": 24, + "columnNumber": 25, + "label": "PARAM" + }, + "definedBy": { + "name": "filename", + "typeFullName": "ANY", + "position": 1, + "lineNumber": 24, + "columnNumber": 25, + "label": "PARAM" + }, + "invokedCalls": [ + { + "callName": "join", + "resolvedMethod": null, + "paramTypes": [ + "ANY", + "ANY" + ], + "returnType": "ANY", + "isExternal": null, + "lineNumber": 26, + "columnNumber": 16 + } + ], + "argToCalls": [] + } + ] + }, + { + "code": "", + "fullName": "app.py:.unrelated_top_level_function", + "signature": "", + "fileName": "app.py", + "lineNumber": 37, + "columnNumber": 1, + "usages": [ + { + "targetObj": { + "name": "unrelated_top_level_function", + "typeFullName": "ANY", + "resolvedMethod": "app.py:.unrelated_top_level_function", + "isExternal": false, + "lineNumber": 37, + "columnNumber": 1, + "label": "CALL" + }, + "definedBy": { + "name": "unrelated_top_level_function", + "typeFullName": "ANY", + "resolvedMethod": "app.py:.unrelated_top_level_function", + "isExternal": false, + "lineNumber": 37, + "columnNumber": 1, + "label": "CALL" + }, + "invokedCalls": [], + "argToCalls": [] + } + ] + }, + { + "code": "", + "fullName": "app.py:", + "signature": "", + "fileName": "app.py", + "lineNumber": 1, + "columnNumber": 1, + "usages": [ + { + "targetObj": { + "name": "__init__", + "typeFullName": "", + "resolvedMethod": "flask.py:.Flask.__init__", + "isExternal": true, + "lineNumber": 1, + "columnNumber": 1, + "label": "CALL" + }, + "definedBy": { + "name": "__init__", + "typeFullName": "", + "resolvedMethod": "flask.py:.Flask.__init__", + "isExternal": true, + "lineNumber": 1, + "columnNumber": 1, + "label": "CALL" + }, + "invokedCalls": [ + { + "callName": "Flask", + "resolvedMethod": "flask.py:.Flask.__init__", + "paramTypes": [], + "returnType": "", + "isExternal": true, + "lineNumber": 5, + "columnNumber": 7 + } + ], + "argToCalls": [] + } + ] + }, + { + "code": "", + "fullName": "app.py:.ReportGenerator.", + "signature": "", + "fileName": "app.py", + "lineNumber": 13, + "columnNumber": 18, + "usages": [ + { + "targetObj": { + "name": "class ReportGenerator", + "typeFullName": "", + "resolvedMethod": "class ReportGenerator", + "isExternal": true, + "lineNumber": 13, + "columnNumber": 18, + "label": "CALL" + }, + "definedBy": { + "name": "class ReportGenerator", + "typeFullName": "", + "resolvedMethod": "class ReportGenerator", + "isExternal": true, + "lineNumber": 13, + "columnNumber": 18, + "label": "CALL" + }, + "invokedCalls": [ + { + "callName": "", + "resolvedMethod": "class ReportGenerator", + "paramTypes": [], + "returnType": "", + "isExternal": true, + "lineNumber": 13, + "columnNumber": 18 + } + ], + "argToCalls": [] + } + ] + }, + { + "code": "", + "fullName": "app.py:.ReportGenerator.", + "signature": "", + "fileName": "app.py", + "lineNumber": 13, + "columnNumber": 18, + "usages": [ + { + "targetObj": { + "name": "__init__", + "typeFullName": "", + "resolvedMethod": "app.py:.ReportGenerator.__init__", + "isExternal": true, + "lineNumber": 13, + "columnNumber": 18, + "label": "CALL" + }, + "definedBy": { + "name": "__init__", + "typeFullName": "", + "resolvedMethod": "app.py:.ReportGenerator.__init__", + "isExternal": true, + "lineNumber": 13, + "columnNumber": 18, + "label": "CALL" + }, + "invokedCalls": [ + { + "callName": "__init__", + "resolvedMethod": "app.py:.ReportGenerator.__init__", + "paramTypes": [], + "returnType": "", + "isExternal": true, + "lineNumber": 13, + "columnNumber": 18 + } + ], + "argToCalls": [] + } + ] + }, + { + "code": "", + "fullName": "app.py:.ReportGenerator.__init__", + "signature": "", + "fileName": "app.py", + "lineNumber": 13, + "columnNumber": 5, + "usages": [ + { + "targetObj": { + "name": "__init__", + "typeFullName": "", + "resolvedMethod": "app.py:.ReportGenerator.__init__", + "isExternal": true, + "lineNumber": 13, + "columnNumber": 5, + "label": "CALL" + }, + "definedBy": { + "name": "__init__", + "typeFullName": "", + "resolvedMethod": "app.py:.ReportGenerator.__init__", + "isExternal": true, + "lineNumber": 13, + "columnNumber": 5, + "label": "CALL" + }, + "invokedCalls": [ + { + "callName": "app.py:.ReportGenerator.__init__", + "resolvedMethod": "app.py:.ReportGenerator.__init__", + "paramTypes": [], + "returnType": "", + "isExternal": true, + "lineNumber": 13, + "columnNumber": 5 + } + ], + "argToCalls": [] + } + ] + }, + { + "code": "", + "fullName": "app.py:", + "signature": "", + "fileName": "app.py", + "lineNumber": 1, + "columnNumber": 1, + "usages": [ + { + "targetObj": { + "name": "def ", + "typeFullName": "", + "resolvedMethod": "def ", + "isExternal": true, + "lineNumber": 1, + "columnNumber": 1, + "label": "CALL" + }, + "definedBy": { + "name": "def ", + "typeFullName": "", + "resolvedMethod": "def ", + "isExternal": true, + "lineNumber": 1, + "columnNumber": 1, + "label": "CALL" + }, + "invokedCalls": [ + { + "callName": "", + "resolvedMethod": "def ", + "paramTypes": [], + "returnType": "", + "isExternal": true, + "lineNumber": 10, + "columnNumber": 1 + } + ], + "argToCalls": [] + } + ] + }, + { + "code": "", + "fullName": "app.py:.handle_request", + "signature": "", + "fileName": "app.py", + "lineNumber": 18, + "columnNumber": 1, + "usages": [ + { + "targetObj": { + "name": "__init__", + "typeFullName": "", + "resolvedMethod": "app.py:.ReportGenerator.__init__", + "isExternal": true, + "lineNumber": 18, + "columnNumber": 1, + "label": "CALL" + }, + "definedBy": { + "name": "__init__", + "typeFullName": "", + "resolvedMethod": "app.py:.ReportGenerator.__init__", + "isExternal": true, + "lineNumber": 18, + "columnNumber": 1, + "label": "CALL" + }, + "invokedCalls": [ + { + "callName": "ReportGenerator", + "resolvedMethod": "app.py:.ReportGenerator.__init__", + "paramTypes": [], + "returnType": "", + "isExternal": true, + "lineNumber": 20, + "columnNumber": 17 + } + ], + "argToCalls": [] + } + ] + }, + { + "code": "", + "fullName": "app.py:.handle_request", + "signature": "", + "fileName": "app.py", + "lineNumber": 18, + "columnNumber": 1, + "usages": [ + { + "targetObj": { + "name": "build_file_path", + "typeFullName": "", + "resolvedMethod": "app.py:.handle_request.build_file_path", + "isExternal": true, + "lineNumber": 18, + "columnNumber": 1, + "label": "CALL" + }, + "definedBy": { + "name": "build_file_path", + "typeFullName": "", + "resolvedMethod": "app.py:.handle_request.build_file_path", + "isExternal": true, + "lineNumber": 18, + "columnNumber": 1, + "label": "CALL" + }, + "invokedCalls": [ + { + "callName": "build_file_path", + "resolvedMethod": "app.py:.handle_request.build_file_path", + "paramTypes": [], + "returnType": "", + "isExternal": true, + "lineNumber": 31, + "columnNumber": 19 + } + ], + "argToCalls": [] + } + ] + }, + { + "code": "", + "fullName": "app.py:.handle_request", + "signature": "", + "fileName": "app.py", + "lineNumber": 18, + "columnNumber": 1, + "usages": [ + { + "targetObj": { + "name": "read", + "typeFullName": "", + "resolvedMethod": "f.read", + "isExternal": true, + "lineNumber": 18, + "columnNumber": 1, + "label": "CALL" + }, + "definedBy": { + "name": "read", + "typeFullName": "", + "resolvedMethod": "f.read", + "isExternal": true, + "lineNumber": 18, + "columnNumber": 1, + "label": "CALL" + }, + "invokedCalls": [ + { + "callName": "read", + "resolvedMethod": "f.read", + "paramTypes": [], + "returnType": "", + "isExternal": true, + "lineNumber": 34, + "columnNumber": 16 + } + ], + "argToCalls": [] + } + ] + }, + { + "code": "", + "fullName": "app.py:.handle_request", + "signature": "", + "fileName": "app.py", + "lineNumber": 18, + "columnNumber": 1, + "usages": [ + { + "targetObj": { + "name": "open", + "typeFullName": "", + "resolvedMethod": "__builtin.open", + "isExternal": true, + "lineNumber": 18, + "columnNumber": 1, + "label": "CALL" + }, + "definedBy": { + "name": "open", + "typeFullName": "", + "resolvedMethod": "__builtin.open", + "isExternal": true, + "lineNumber": 18, + "columnNumber": 1, + "label": "CALL" + }, + "invokedCalls": [ + { + "callName": "open", + "resolvedMethod": "__builtin.open", + "paramTypes": [], + "returnType": "", + "isExternal": true, + "lineNumber": 33, + "columnNumber": 10 + } + ], + "argToCalls": [] + } + ] + }, + { + "code": "", + "fullName": "app.py:", + "signature": "", + "fileName": "app.py", + "lineNumber": 1, + "columnNumber": 1, + "usages": [ + { + "targetObj": { + "name": "route", + "typeFullName": "", + "resolvedMethod": "flask.py:.Flask.route", + "isExternal": true, + "lineNumber": 1, + "columnNumber": 1, + "label": "CALL" + }, + "definedBy": { + "name": "route", + "typeFullName": "", + "resolvedMethod": "flask.py:.Flask.route", + "isExternal": true, + "lineNumber": 1, + "columnNumber": 1, + "label": "CALL" + }, + "invokedCalls": [ + { + "callName": "route", + "resolvedMethod": "flask.py:.Flask.route", + "paramTypes": [], + "returnType": "", + "isExternal": true, + "lineNumber": 17, + "columnNumber": 2 + } + ], + "argToCalls": [] + } + ] + }, + { + "code": "", + "fullName": "app.py:", + "signature": "", + "fileName": "app.py", + "lineNumber": 1, + "columnNumber": 1, + "usages": [ + { + "targetObj": { + "name": "route", + "typeFullName": "", + "resolvedMethod": "app.route", + "isExternal": true, + "lineNumber": 1, + "columnNumber": 1, + "label": "CALL" + }, + "definedBy": { + "name": "route", + "typeFullName": "", + "resolvedMethod": "app.route", + "isExternal": true, + "lineNumber": 1, + "columnNumber": 1, + "label": "CALL" + }, + "invokedCalls": [ + { + "callName": "", + "resolvedMethod": "app.route", + "paramTypes": [], + "returnType": "", + "isExternal": true, + "lineNumber": 17, + "columnNumber": 2 + } + ], + "argToCalls": [] + } + ] + } + ], + "userDefinedTypes": [ + { + "name": "app.py:.ReportGenerator", + "fields": [ + { + "name": "", + "typeFullName": "app.py:.ReportGenerator.", + "lineNumber": null, + "columnNumber": null, + "label": "LOCAL" + }, + { + "name": "__init__", + "typeFullName": "app.py:.ReportGenerator.__init__", + "lineNumber": 13, + "columnNumber": 5, + "label": "LOCAL" + } + ], + "procedures": [ + { + "callName": "", + "resolvedMethod": "app.py:.ReportGenerator.", + "paramTypes": [ + "ANY" + ], + "returnType": "app.py:.ReportGenerator", + "isExternal": false, + "lineNumber": 13, + "columnNumber": 18 + }, + { + "callName": "", + "resolvedMethod": "app.py:.ReportGenerator.", + "paramTypes": [ + "app.py:.ReportGenerator", + "ANY" + ], + "returnType": "ANY", + "isExternal": false, + "lineNumber": 13, + "columnNumber": 18 + }, + { + "callName": "__init__", + "resolvedMethod": "app.py:.ReportGenerator.__init__", + "paramTypes": [ + "app.py:.ReportGenerator", + "ANY", + "ANY" + ], + "returnType": "ANY", + "isExternal": false, + "lineNumber": 13, + "columnNumber": 5 + } + ], + "fileName": "app.py", + "lineNumber": 10, + "columnNumber": 1 + }, + { + "name": "app.py:.ReportGenerator", + "fields": [ + { + "name": "base_dir", + "typeFullName": "ANY", + "lineNumber": 14, + "columnNumber": 9, + "label": "LOCAL" + }, + { + "name": "__init__", + "typeFullName": "app.py:.ReportGenerator.__init__", + "lineNumber": 13, + "columnNumber": 5, + "label": "LOCAL" + } + ], + "procedures": [ + { + "callName": "", + "resolvedMethod": "app.py:.ReportGenerator.", + "paramTypes": [], + "returnType": "ANY", + "isExternal": false, + "lineNumber": 10, + "columnNumber": 1 + } + ], + "fileName": "app.py", + "lineNumber": 10, + "columnNumber": 1 + }, + { + "name": "route", + "fields": [ + { + "name": "\"/report\"", + "typeFullName": "ANY", + "lineNumber": 17, + "columnNumber": 12, + "label": "LOCAL" + } + ], + "procedures": [ + { + "callName": "route", + "resolvedMethod": "flask.py:.Flask.route", + "paramTypes": [ + "ANY", + "ANY", + "ANY" + ], + "returnType": "ANY", + "isExternal": true, + "lineNumber": null, + "columnNumber": null + }, + { + "callName": "app.route", + "resolvedMethod": "app.route(\"/report\")", + "paramTypes": [ + "app", + "/report" + ], + "returnType": "", + "isExternal": true, + "lineNumber": 17, + "columnNumber": 2 + }, + { + "callName": "app.route", + "resolvedMethod": "app.route(\"/report\")", + "paramTypes": [ + "app", + "/report" + ], + "returnType": "", + "isExternal": true, + "lineNumber": 17, + "columnNumber": 2 + } + ], + "fileName": "app.py", + "lineNumber": 17, + "columnNumber": 2 + } + ] +} \ No newline at end of file diff --git a/tests/test_atom_reachability_tools.py b/tests/test_atom_reachability_tools.py new file mode 100644 index 0000000..a07cac4 --- /dev/null +++ b/tests/test_atom_reachability_tools.py @@ -0,0 +1,326 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# http://nexb.com and https://github.com/aboutcode-org/scancode.io +# The ScanCode.io software is licensed under the Apache License version 2.0. +# Data generated with ScanCode.io is provided as-is without warranties. +# ScanCode is a trademark of nexB Inc. +# +# You may not use this software except in compliance with the License. +# You may obtain a copy of the License at: http://apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software distributed +# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# +# Data Generated with ScanCode.io is provided on an "AS IS" BASIS, WITHOUT WARRANTIES +# OR CONDITIONS OF ANY KIND, either express or implied. No content created from +# ScanCode.io should be considered or used as legal advice. Consult an Attorney +# for any legal advice. +# +# ScanCode.io is a free software code scanning tool from nexB Inc. and others. +# Visit https://github.com/aboutcode-org/scancode.io for support and download. + +import json +import shutil +from pathlib import Path + +from django.test import TestCase + +from scanpipe.models import Project +from scanpipe.pipes import collect_and_create_codebase_resources + +from scanpipe.pipes.reachability import ReachabilityStatus + +from reachablecode.pipes.atom import normalize_file_path, normalize_symbol_name, collect_and_match_resources, \ + UsagesMatcher, ReachablesMatcher + +TEST_DATA_PYTHON = ( + Path(__file__).parent.parent / "data" / "atom_reachability" / "python" +) + +USAGES_JSON = "atom_reachability.python.usages.slices.json" +REACHABLES_JSON = "atom_reachability.python.reachables.slices.json" + +COMMIT_HASH = "07ec0de1964b14bf085a1c9a27ece2b61ab6105c" +VCS_URL = "https://github.com/aboutcode-org/test" + + +class UsagesMatcherTests(TestCase): + """Match patch symbols against the real atom ``usages`` slice for ``app.py``.""" + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.slices = json.loads((TEST_DATA_PYTHON / USAGES_JSON).read_text()) + cls.matcher = UsagesMatcher(cls.slices) + cls.names = cls.matcher.by_file.get("app.py") or set() + + def test_matches_expected_app_py_symbols(self): + self.assertIn("app.py", self.matcher.by_file) + + self.assertIn("app.handle_request.build_file_path", self.names) + self.assertIn("app.handle_request", self.names) + self.assertIn("app.ReportGenerator", self.names) + self.assertIn("app.unrelated_top_level_function", self.names) + + self.assertIn( + "build_file_path", + self.matcher.match_symbols({"build_file_path"}, "app.py"), + ) + self.assertIn( + "handle_request.build_file_path", + self.matcher.match_symbols({"handle_request.build_file_path"}, "app.py"), + ) + self.assertIn( + "app.py::handle_request.build_file_path", + self.matcher.match_symbols( + {"app.py::handle_request.build_file_path"}, "app.py" + ), + ) + self.assertIn( + "app.py::ReportGenerator", + self.matcher.match_symbols({"app.py::ReportGenerator"}, "app.py"), + ) + self.assertIn( + "app.py::ReportGenerator.__init__", + self.matcher.match_symbols({"app.py::ReportGenerator.__init__"}, "app.py"), + ) + self.assertIn( + "build_file_path", + self.matcher.match_symbols({"build_file_path"}, "python/app.py"), + ) + self.assertFalse( + self.matcher.match_symbols({"symbol_that_does_not_exist"}, "app.py") + ) + + +class ReachablesMatcherTests(TestCase): + """Match patch symbols against the real atom ``reachables`` slice for ``app.py``.""" + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.slices = json.loads((TEST_DATA_PYTHON / REACHABLES_JSON).read_text()) + cls.matcher = ReachablesMatcher(cls.slices) + + def test_matches_expected_app_py_symbols(self): + self.assertIn("app.py", self.matcher.by_file) + + self.assertIn( + "build_file_path", + self.matcher.match_symbols({"build_file_path"}, "app.py"), + ) + self.assertIn( + "handle_request.build_file_path", + self.matcher.match_symbols({"handle_request.build_file_path"}, "app.py"), + ) + self.assertIn( + "build_file_path", + self.matcher.match_symbols({"build_file_path"}, "python/app.py"), + ) + self.assertFalse( + self.matcher.match_symbols({"symbol_that_does_not_exist"}, "app.py") + ) + + orphan = ReachablesMatcher( + [{"flows": [{"parentMethodName": "x"}], "purls": []}] + ) + self.assertEqual(0, orphan.file_count) + + +class NormalizeTests(TestCase): + """``normalize_file_path`` and ``normalize_symbol_name``.""" + + def test_normalize_file_path(self): + self.assertEqual("app.py", normalize_file_path("app.py")) + self.assertEqual("python/app.py", normalize_file_path("python/app.py")) + self.assertEqual("app.py", normalize_file_path("./app.py")) + self.assertEqual("app.py", normalize_file_path("./././app.py")) + self.assertEqual("pkg/app.py", normalize_file_path("./pkg/app.py")) + self.assertEqual("pkg/app.py", normalize_file_path("/pkg/app.py")) + self.assertEqual("pkg/app.py", normalize_file_path("//pkg/app.py")) + self.assertEqual("pkg/app.py", normalize_file_path(r"pkg\app.py")) + self.assertEqual("pkg/app.py", normalize_file_path(r".\pkg\app.py")) + self.assertEqual("", normalize_file_path("")) + self.assertEqual("", normalize_file_path(None)) + + def test_normalize_symbol_name(self): + self.assertEqual("build_file_path", normalize_symbol_name("build_file_path")) + self.assertEqual( + "serve_report.build_file_path", + normalize_symbol_name("serve_report.build_file_path"), + ) + self.assertEqual( + "app.handle_request.build_file_path", + normalize_symbol_name("app.py:.handle_request.build_file_path"), + ) + self.assertEqual( + "pkg.app.ReportGenerator.__init__", + normalize_symbol_name("pkg/app.py:.ReportGenerator.__init__"), + ) + + self.assertEqual( + "ReportGenerator.__init__", + normalize_symbol_name("app.py::ReportGenerator.__init__"), + ) + + self.assertEqual( + "app.ReportGenerator.__init__", + normalize_symbol_name("app.py:.ReportGenerator.__init__"), + ) + self.assertEqual( + "app.ReportGenerator.__init__", + normalize_symbol_name( + "app.py:.ReportGenerator.__init__" + ), + ) + self.assertEqual( + "app.ReportGenerator.__init__", + normalize_symbol_name( + "app.py:.ReportGenerator.__init__" + ), + ) + self.assertEqual( + "app.ReportGenerator", + normalize_symbol_name("app.py:.ReportGenerator."), + ) + self.assertEqual("def ", normalize_symbol_name("def ")) + + self.assertEqual("", normalize_symbol_name("")) + self.assertEqual("", normalize_symbol_name("")) + self.assertEqual("", normalize_symbol_name("")) + self.assertEqual("", normalize_symbol_name("")) + + self.assertEqual("", normalize_symbol_name(".equals")) + self.assertEqual("", normalize_symbol_name(".equals")) + self.assertEqual("", normalize_symbol_name("")) + self.assertEqual("", normalize_symbol_name(".assignment")) + self.assertEqual("", normalize_symbol_name(".alloc")) + + +class CollectAndMatchResourcesTests(TestCase): + def setUp(self): + self.project = Project.objects.create(name="Analysis") + self.project.codebase_path.mkdir(parents=True, exist_ok=True) + + def _prepare_resources(self): + shutil.copytree(TEST_DATA_PYTHON, self.project.codebase_path / "python") + collect_and_create_codebase_resources(self.project) + for resource in self.project.codebaseresources.all(): + if resource.path.endswith(".py"): + resource.programming_language = "Python" + resource.save() + + def _load_atom_slices(self): + """Load both atom slice fixtures: usages and reachables.""" + usages = json.loads((TEST_DATA_PYTHON / USAGES_JSON).read_text()) + reachables = json.loads((TEST_DATA_PYTHON / REACHABLES_JSON).read_text()) + return usages, reachables + + def _run_atom( + self, + resources, + usages_slices, + reachables_slices, + vulnerable, + fixed, + advisory_uid, + ): + patch_symbols = { + COMMIT_HASH: { + "Python": { + "vulnerable": { + f"app.py::{key}": meta for key, meta in vulnerable.items() + }, + "fixed": {f"app.py::{key}": meta for key, meta in fixed.items()}, + } + } + } + patches = [ + { + "vcs_url": VCS_URL, + "commit_hash": COMMIT_HASH, + "advisory_uids": [advisory_uid], + } + ] + + collect_and_match_resources( + tool_name="atom", + patches=patches, + patch_symbols=patch_symbols, + usages_slices=usages_slices, + reachables_slices=reachables_slices, + candidate_resources=resources, + ) + + for resource in resources: + resource.refresh_from_db() + return resources + + @staticmethod + def _get_reports(resource): + return (resource.extra_data or {}).get("symbols_reachability") or [] + + def test_build_file_path_reachable_through_reachables_slice(self): + """ + ``build_file_path`` is indexed in the real usages slice and marked + reachable by the real reachables slice, so the report is REACHABLE. + """ + self._prepare_resources() + resource = self.project.codebaseresources.get(path="python/app.py") + self.assertEqual("Python", resource.programming_language) + + usages_slices, reachables_slices = self._load_atom_slices() + self.assertTrue(usages_slices.get("objectSlices")) + self.assertTrue(reachables_slices) + + vulnerable = { + "build_file_path": { + "qualified_name": "handle_request.build_file_path", + "text": "return os.path.join(generator.base_dir, filename)", + } + } + fixed = { + "build_file_path": { + "qualified_name": "handle_request.build_file_path", + "text": ( + "return os.path.join(" + "generator.base_dir, os.path.basename(filename))" + ), + } + } + + self._run_atom( + [resource], + usages_slices, + reachables_slices, + vulnerable, + fixed, + advisory_uid="pypi/app/PYSEC-0003", + ) + + results = self._get_reports(resource) + self.assertEqual(1, len(results)) + result = results[0] + + self.assertEqual(["pypi/app/PYSEC-0003"], result["advisory_uids"]) + self.assertEqual(COMMIT_HASH, result["patch"]["commit_hash"]) + self.assertEqual(VCS_URL, result["patch"]["vcs_url"]) + + symbol_key = "app.py::build_file_path" + self.assertEqual([symbol_key], result["vulnerable_symbols"]) + self.assertEqual([symbol_key], result["fixed_symbols"]) + self.assertEqual(ReachabilityStatus.REACHABLE.value, result["is_reachable"]) + + self.assertEqual(1, len(result["tool_details"])) + detail = result["tool_details"][0] + + self.assertEqual(symbol_key, detail["symbol_name"]) + self.assertTrue(detail["in_usage"]) + self.assertTrue(detail["in_reachable"]) + + self.assertTrue(detail["calls"]) + call_names = {call["call_name"] for call in detail["calls"]} + self.assertIn("build_file_path", call_names) + self.assertIn("open", call_names) + self.assertNotIn("is_test", result) From cd8d5c62e6cbc3bbea7b8785692c2d0fd791e15c Mon Sep 17 00:00:00 2001 From: ziad hany Date: Wed, 28 Oct 2026 20:12:10 +0300 Subject: [PATCH 2/9] Update the README.md file Add Docker Compose file Signed-off-by: ziadhany --- README.rst | 175 +++++++++++------- atom.Dockerfile | 43 +++++ docker-compose.rechable.yml | 131 +++++++++++++ ...al_atom_reachability_integration_tools.rst | 115 ++++++++++++ tests/test_atom_reachability_tools.py | 3 +- "\342\200\216docker.env" | 10 + 6 files changed, 406 insertions(+), 71 deletions(-) create mode 100644 atom.Dockerfile create mode 100644 docker-compose.rechable.yml create mode 100644 docs/source/contribute/tutorial_atom_reachability_integration_tools.rst create mode 100644 "\342\200\216docker.env" diff --git a/README.rst b/README.rst index 11a4dfb..b839e22 100644 --- a/README.rst +++ b/README.rst @@ -1,107 +1,144 @@ -A Simple Python Project Skeleton -================================ +================= +reachablecode +================= -This repo attempts to standardize the structure of the Python-based project's -repositories using modern Python packaging and configuration techniques. -Using this `blog post`_ as inspiration, this repository serves as the base for -all new Python projects and is mergeable in existing repositories as well. +reachablecode is a collection of pipelines for analyzing code reachability. +It integrates with ScanCode.io as a pipeline plugin and uses tools like (Atom, CPG, .. ) to determine whether +vulnerable code symbols are reachable within a project's codebase. -.. _blog post: https://blog.jaraco.com/a-project-skeleton-for-python-projects/ +Other information: +- License: Apache-2.0 +- Copyright (c) nexB Inc., AboutCode.org and others. +- Homepage: https://github.com/aboutcode-org/reachablecode/ -Usage -===== -A brand new project -------------------- +Requirements +------------ -.. code-block:: bash +- ScanCode.io https://github.com/nexB/scancode.io - git init my-new-repo - cd my-new-repo - git pull git@github.com:nexB/skeleton +See also the next section for detailed instructions. - # Create the new repo on GitHub, then update your remote - git remote set-url origin git@github.com:nexB/your-new-repo.git -From here, you can make the appropriate changes to the files for your specific project. +Local installation and usage test +------------------------------------ -Update an existing project ---------------------------- +To install: +~~~~~~~~~~~~ -.. code-block:: bash +- Clone reachablecode locally side-by-side: ``git clone https://github.com/aboutcode-org/reachablecode`` +- Clone ScanCode.io locally side-by-side: ``git clone https://github.com/aboutcode-org/scancode.io`` +- Change to the scancode.io directory and run ``make dev`` then ``source bin/activate`` - cd my-existing-project - git remote add skeleton git@github.com:nexB/skeleton - git fetch skeleton - git merge skeleton/main --allow-unrelated-histories + - Follow the full instructions at https://scancodeio.readthedocs.io/en/latest/installation.html#local-development-installation -This is also the workflow to use when updating the skeleton files in any given repository. +- Run ``pip install --editable ../reachablecode`` +- Run ScanCode.io with ``./manage.py runserver --insecure`` and open the URL in your browser. + There is a new "android_d2d" pipeline available when creating a new project. -More usage instructions can be found in ``docs/skeleton-usage.rst``. +Development +---------------- +- Install requirements and dependencies using ``make dev`` +- Then ``source venv/bin/activate`` -Release Notes -============= +Testing: -- 2025-03-31: +- To run tests: ``pytest -vvs`` - - Use ruff as the main code formatting tool, add ruff rules to pyproject.toml -- 2025-03-29: +Acknowledgements, Funding, Support and Sponsoring +-------------------------------------------------------- - - Add support for beta macOS-15 - - Add support for beta windows-2025 +This project is funded, supported and sponsored by: -- 2025-02-14: +- Generous support and contributions from users like you! +- the European Commission NGI programme +- the NLnet Foundation +- the Swiss State Secretariat for Education, Research and Innovation (SERI) +- Google, including the Google Summer of Code and the Google Seasons of Doc programmes +- Mercedes-Benz Group +- Microsoft and Microsoft Azure +- AboutCode ASBL +- nexB Inc. - - Drop support for Python 3.8, add support in CI for Python 3.13, use Python 3.12 as default - version. -- 2025-01-17: - - Drop support for macOS-12, add support for macOS-14 - - Add support in CI for ubuntu-24.04 - - Add support in CI for Python 3.12 +|europa| |dgconnect| -- 2024-08-20: +|ngi| |nlnet| - - Update references of ownership from nexB to aboutcode-org +|aboutcode| |nexb| -- 2024-07-01: - - Drop support for Python 3.8 - - Drop support for macOS-11, add support for macOS-14 +This project was funded through the NGI0 Core Fund, a fund established by NLnet with financial +support from the European Commission's Next Generation Internet programme, under the aegis of DG +Communications Networks, Content and Technology under grant agreement No 101092990. -- 2024-02-19: +|ngizerocore| https://nlnet.nl/project/Back2source-next/ - - Replace support in CI of default ubuntu-20.04 by ubuntu-22.04 +.. |nlnet| image:: https://nlnet.nl/logo/banner.png + :target: https://nlnet.nl + :height: 50 + :alt: NLnet foundation logo -- 2023-10-18: +.. |ngi| image:: https://ngi.eu/wp-content/uploads/thegem-logos/logo_8269bc6efcf731d34b6385775d76511d_1x.png + :target: https://ngi.eu35 + :height: 50 + :alt: NGI logo - - Add dark mode support in documentation +.. |nexb| image:: https://nexb.com/wp-content/uploads/2022/04/nexB.svg + :target: https://nexb.com + :height: 30 + :alt: nexB logo -- 2023-07-18: +.. |europa| image:: https://ngi.eu/wp-content/uploads/sites/77/2017/10/bandiera_stelle.png + :target: http://ec.europa.eu/index_en.htm + :height: 40 + :alt: Europa logo - - Add macOS-13 job in azure-pipelines.yml +.. |aboutcode| image:: https://aboutcode.org/wp-content/uploads/2023/10/AboutCode.svg + :target: https://aboutcode.org/ + :height: 30 + :alt: AboutCode logo -- 2022-03-04: +.. |swiss| image:: https://www.sbfi.admin.ch/sbfi/en/_jcr_content/logo/image.imagespooler.png/1493119032540/logo.png + :target: https://www.sbfi.admin.ch/sbfi/en/home/seri/seri.html + :height: 40 + :alt: Swiss logo - - Synchronize configure and configure.bat scripts for sanity - - Update CI operating system support with latest Azure OS images - - Streamline utility scripts in etc/scripts/ to create, fetch and manage third-party - dependencies. There are now fewer scripts. See etc/scripts/README.rst for details +.. |dgconnect| image:: https://commission.europa.eu/themes/contrib/oe_theme/dist/ec/images/logo/positive/logo-ec--en.svg + :target: https://commission.europa.eu/about-european-commission/departments-and-executive-agencies/communications-networks-content-and-technology_en + :height: 40 + :alt: EC DG Connect logo -- 2021-09-03: - - ``configure`` now requires pinned dependencies via the use of ``requirements.txt`` - and ``requirements-dev.txt`` - - ``configure`` can now accept multiple options at once - - Add utility scripts from scancode-toolkit/etc/release/ for use in generating project files - - Rename virtual environment directory from ``tmp`` to ``venv`` - - Update README.rst with instructions for generating ``requirements.txt`` - and ``requirements-dev.txt``, as well as collecting dependencies as wheels and generating - ABOUT files for them. +.. |ngizerocore| image:: https://nlnet.nl/image/logos/NGI0_tag.svg + :target: https://nlnet.nl/core + :height: 40 + :alt: NGI Zero Core Logo -- 2021-05-11: - - Adopt new configure scripts from ScanCode TK that allows correct configuration of which - Python version is used. +.. |ngizerocommons| image:: https://nlnet.nl/image/logos/NGI0_tag.svg + :target: https://nlnet.nl/commonsfund/ + :height: 40 + :alt: NGI Zero Commons Logo + +.. |ngizeropet| image:: https://nlnet.nl/image/logos/NGI0PET_tag.svg + :target: https://nlnet.nl/PET + :height: 40 + :alt: NGI Zero PET logo + +.. |ngizeroentrust| image:: https://nlnet.nl/image/logos/NGI0Entrust_tag.svg + :target: https://nlnet.nl/entrust + :height: 38 + :alt: NGI Zero Entrust logo + +.. |ngiassure| image:: https://nlnet.nl/image/logos/NGIAssure_tag.svg + :target: https://nlnet.nl/image/logos/NGIAssure_tag.svg + :height: 32 + :alt: NGI Assure logo + +.. |ngidiscovery| image:: https://nlnet.nl/image/logos/NGI0Discovery_tag.svg + :target: https://nlnet.nl/discovery/ + :height: 40 + :alt: NGI Discovery logo diff --git a/atom.Dockerfile b/atom.Dockerfile new file mode 100644 index 0000000..b9df9a7 --- /dev/null +++ b/atom.Dockerfile @@ -0,0 +1,43 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# http://nexb.com and https://github.com/aboutcode-org/scancode.io +# The ScanCode.io software is licensed under the Apache License version 2.0. +# Data generated with ScanCode.io is provided as-is without warranties. +# ScanCode is a trademark of nexB Inc. +# +# You may not use this software except in compliance with the License. +# You may obtain a copy of the License at: http://apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software distributed +# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +# CONDITIONS OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# +# Data Generated with ScanCode.io is provided on an "AS IS" BASIS, WITHOUT WARRANTIES +# OR CONDITIONS OF ANY KIND, either express or implied. No content created from +# ScanCode.io should be considered or used as legal advice. Consult an Attorney +# for any legal advice. +# +# ScanCode.io is a free software code scanning tool from nexB Inc. and others. +# Visit https://github.com/aboutcode-org/scancode.io for support and download. + +FROM ghcr.io/appthreat/atom:v3.2.0 + +RUN set -e; \ + JAVA_DIR="$(ls -d /opt/java/*-tem | head -n1)"; \ + MAVEN_DIR="$(ls -d /opt/maven/* | head -n1)"; \ + SBT_DIR="$(ls -d /opt/sbt/* | head -n1)"; \ + SCALA_DIR="$(ls -d /opt/scala/* | head -n1)"; \ + mkdir -p /atom-staged/opt /atom-staged/bin /atom-staged/lib/node_modules; \ + cp -a /opt/wrapper /opt/java /opt/maven /opt/sbt /opt/scala /opt/vendor /atom-staged/opt/; \ + cp /usr/bin/node-22 /atom-staged/bin/node; \ + for lib in $(ldd /usr/bin/node-22 | awk '/=>/{print $3}'); do \ + case "$lib" in */libc.so*|*/libm.so*|*/libpthread*|*/libdl.so*|*/librt*|*/ld-linux*) ;; *) cp -aL "$lib" /atom-staged/lib/ ;; esac; \ + done; \ + cp -a /usr/local/lib/node_modules/. /atom-staged/lib/node_modules/; \ + ln -sfn /atom-root/opt/wrapper/nodejs/packages/atom /atom-root/opt/wrapper/nodejs/packages/atom-jar /atom-staged/lib/node_modules/@appthreat/; \ + printf '#!/bin/sh\nexport LD_LIBRARY_PATH=/atom-root/lib\nexport JAVA_HOME=/atom-root%s\nexport PATH=/atom-root%s/bin:/atom-root%s/bin:/atom-root%s/bin:/atom-root%s/bin:/atom-root/opt/vendor/bin:$PATH\nexec /atom-root/bin/node /atom-root/opt/wrapper/nodejs/packages/atom/index.js "$@"\n' \ + "$JAVA_DIR" "$JAVA_DIR" "$MAVEN_DIR" "$SBT_DIR" "$SCALA_DIR" \ + > /atom-staged/bin/atom; \ + chmod +x /atom-staged/bin/atom + +ENTRYPOINT ["/bin/sh", "-c", "set -e; find /atom-root -mindepth 1 -delete; cp -a /atom-staged/. /atom-root/"] \ No newline at end of file diff --git a/docker-compose.rechable.yml b/docker-compose.rechable.yml new file mode 100644 index 0000000..9a31da3 --- /dev/null +++ b/docker-compose.rechable.yml @@ -0,0 +1,131 @@ +name: scancodeio + +x-description: "Full ScanCode.io stack: all services, local image build" + +services: + db: + image: docker.io/library/postgres:17.11 + env_file: + - docker.env + volumes: + - db_data:/var/lib/postgresql/data/ + shm_size: "1gb" + restart: always + healthcheck: + test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] + interval: 10s + timeout: 5s + retries: 5 + + redis: + image: docker.io/library/redis:8.10.2-alpine + # Enable redis data persistence using the "Append Only File" with the + # default policy of fsync every second. See https://redis.io/topics/persistence + command: redis-server --appendonly yes --loglevel warning + volumes: + - redis_data:/data + restart: always + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + + web: + build: + context: . + command: sh -c " + ./manage.py migrate && + ./manage.py collectstatic --no-input --verbosity 0 --clear && + gunicorn scancodeio.wsgi:application --bind :8000 --timeout 600 \ + --workers ${GUNICORN_WORKERS:-8} --worker-tmp-dir /dev/shm" + env_file: + - docker.env + expose: + - 8000 + volumes: + - .env:/opt/scancodeio/.env + - /etc/scancodeio/:/etc/scancodeio/ + - workspace:/var/scancodeio/workspace/ + - static:/var/scancodeio/static/ + - healthycode_venv:/opt/healthycode + - atom_root:/atom-root + restart: always + depends_on: + db: + condition: service_healthy + redis: + condition: service_healthy + + worker: + build: + context: . + # Ensure that potential db migrations run first by waiting until "web" is up + command: wait-for-it --strict --timeout=600 web:8000 -- sh -c " + ./manage.py rqworker --worker-class scancodeio.worker.ScanCodeIOWorker + --queue-class scancodeio.worker.ScanCodeIOQueue + --verbosity 1" + env_file: + - docker.env + volumes: + - .env:/opt/scancodeio/.env + - /etc/scancodeio/:/etc/scancodeio/ + - workspace:/var/scancodeio/workspace/ + - healthycode_venv:/opt/healthycode + - atom_root:/atom-root + restart: always + depends_on: + db: + condition: service_healthy + redis: + condition: service_healthy + web: + condition: service_started + + nginx: + image: docker.io/library/nginx:1.31.6-alpine + ports: + - "${NGINX_PUBLISHED_HTTP_PORT:-80}:80" + - "${NGINX_PUBLISHED_HTTPS_PORT:-443}:443" + volumes: + - ./etc/nginx/conf.d/:/etc/nginx/conf.d/ + - static:/var/scancodeio/static/ + - webroot:/var/www/html/ + depends_on: + web: + condition: service_started + restart: always + + clamav: + image: docker.io/clamav/clamav:1.5.4_base + volumes: + - clamav_data:/var/lib/clamav + - workspace:/var/scancodeio/workspace/ + restart: always + + healthycode: + profiles: + - healthycode + image: ghcr.io/aboutcode-org/healthycode:0.2.0 + volumes: + - healthycode_venv:/opt/healthycode + restart: "no" + + atom: + profiles: + - atom + build: + context: . + dockerfile: atom.Dockerfile + volumes: [atom_root:/atom-root] + restart: "no" + +volumes: + db_data: + redis_data: + clamav_data: + static: + workspace: + webroot: + healthycode_venv: + atom_root: \ No newline at end of file diff --git a/docs/source/contribute/tutorial_atom_reachability_integration_tools.rst b/docs/source/contribute/tutorial_atom_reachability_integration_tools.rst new file mode 100644 index 0000000..a6cd99e --- /dev/null +++ b/docs/source/contribute/tutorial_atom_reachability_integration_tools.rst @@ -0,0 +1,115 @@ +.. _tutorial_atom_reachability_integration_tools: + +Atom Reachability Tools +======================== + +The ``atom_reachability`` pipeline uses +`Atom `_ to determine whether code symbols +referenced by a vulnerability patch are reachable. Atom is run twice against +the project codebase: + +- ``usages`` extracts local variable and parameter usages from the code and + writes a ``.atom`` file plus a ``usages`` slice. +- ``reachables`` reads that ``.atom`` file and writes a ``reachables`` slice + of the symbols that can actually be reached, based on automatically + generated framework tags. + +Each advisory is classified as ``yes``, ``unknown``, or ``no``. + +Requirements +------------ + +- `Atom tool `_ installed and configured. + +Configuration +------------- + +Set this environment variable in your ``.env``: + +.. code-block:: bash + + ATOM_EXECUTABLE="/home/user/.npm-global/bin/atom" + +Running the pipeline +-------------------- + +1. Open a Project that contains resources and at least one package + vulnerability. +2. Click **"Add pipeline"** and select **"atom_reachability"**. +3. Select **"Execute pipeline now"** and click **"Add pipeline"**. +4. After completion, open the **Resources** view (click the count under + **"RESOURCES"**), then click an affected file and open the **Extra** + tab to view the ``symbols_reachability`` data. + +.. image:: images/tutorial-web-ui-atom-symbol-reachability.png + +Output +------ + +The pipeline writes four timestamped files three JSON documents and one +binary atom file and embeds the same advisory data in each resource's +``extra_data.symbols_reachability``. + +1. ``reachability-YYYY-MM-DD-HH-MM-SS.json`` is the advisory reachability + report. It aggregates results per advisory, listing each advisory with + its overall verdict, matched vulnerable/fixed symbols, and per-symbol + witness paths. + Schema: + +.. code-block:: json + :emphasize-lines: 2, 7, 9, 13 + + { + "purl": "pkg:pypi/b2sdk@1.14.0", + "advisories": [ + { + "advisory_uid": "pypa/b2sdk/PYSEC-2022-33", + "is_reachable": "yes", + "details": [ + { + "symbol_name": "SqliteAccountInfo._create_database", + "in_usage": true, + "calls": [], + "in_reachable": true + } + ], + "vulnerable_symbols": [ + "b2sdk/account_info/sqlite_account_info.py::SqliteAccountInfo", + "b2sdk/account_info/sqlite_account_info.py::SqliteAccountInfo._create_database", + ], + "fixed_symbols": [ + "b2sdk/account_info/sqlite_account_info.py::SqliteAccountInfo", + "b2sdk/account_info/sqlite_account_info.py::SqliteAccountInfo._create_database", + ] + } + ] + } + +2. ``atom_reachability-YYYY-MM-DD-HH-MM-SS.python.usages.slices.json`` is the + ``usages`` slice generated by atom. + +.. code-block:: text + + { + "objectSlices": [...], + "userDefinedTypes": [...] + } + +3. ``atom_reachability-YYYY-MM-DD-HH-MM-SS.python.reachables.slices.json`` is + the ``reachables`` slice generated by atom. + +.. code-block:: text + + [ + { + "flows": [ + ... + ], + "purls": [] + }, + ... + ] + +4. ``atom_reachability-YYYY-MM-DD-HH-MM-SS.python.atom`` is atom's binary + CPG. It can be re-sliced, exported to other tools, + and queried with graph algorithms using the atom CLI. diff --git a/tests/test_atom_reachability_tools.py b/tests/test_atom_reachability_tools.py index a07cac4..1ca4cd7 100644 --- a/tests/test_atom_reachability_tools.py +++ b/tests/test_atom_reachability_tools.py @@ -23,8 +23,7 @@ import json import shutil from pathlib import Path - -from django.test import TestCase +from unittest import TestCase from scanpipe.models import Project from scanpipe.pipes import collect_and_create_codebase_resources diff --git "a/\342\200\216docker.env" "b/\342\200\216docker.env" new file mode 100644 index 0000000..48d19ef --- /dev/null +++ "b/\342\200\216docker.env" @@ -0,0 +1,10 @@ +POSTGRES_DB=scancodeio +POSTGRES_USER=scancodeio +POSTGRES_PASSWORD=scancodeio +POSTGRES_INITDB_ARGS=--encoding=UTF-8 --lc-collate=en_US.UTF-8 --lc-ctype=en_US.UTF-8 + +SCANCODEIO_DB_HOST=db +SCANCODEIO_RQ_REDIS_HOST=redis +SCANCODEIO_ASYNC=True +SCANCODEIO_WORKSPACE_LOCATION=/var/scancodeio/workspace/ +ATOM_EXECUTABLE=/atom-root/bin/atom \ No newline at end of file From 00540a7edabcfb3e737f6c471205887f9f50d6e6 Mon Sep 17 00:00:00 2001 From: ziad hany Date: Wed, 28 Oct 2026 20:35:18 +0300 Subject: [PATCH 3/9] Delete test README.md Update test tree Signed-off-by: ziadhany --- tests/README.rst | 2 -- tests/{ => data}/atom_reachability/python/app.py | 0 .../python/atom_reachability.python.reachables.slices.json | 0 .../python/atom_reachability.python.usages.slices.json | 0 4 files changed, 2 deletions(-) delete mode 100644 tests/README.rst rename tests/{ => data}/atom_reachability/python/app.py (100%) rename tests/{ => data}/atom_reachability/python/atom_reachability.python.reachables.slices.json (100%) rename tests/{ => data}/atom_reachability/python/atom_reachability.python.usages.slices.json (100%) diff --git a/tests/README.rst b/tests/README.rst deleted file mode 100644 index d94783e..0000000 --- a/tests/README.rst +++ /dev/null @@ -1,2 +0,0 @@ -Put your Python test modules in this directory. - diff --git a/tests/atom_reachability/python/app.py b/tests/data/atom_reachability/python/app.py similarity index 100% rename from tests/atom_reachability/python/app.py rename to tests/data/atom_reachability/python/app.py diff --git a/tests/atom_reachability/python/atom_reachability.python.reachables.slices.json b/tests/data/atom_reachability/python/atom_reachability.python.reachables.slices.json similarity index 100% rename from tests/atom_reachability/python/atom_reachability.python.reachables.slices.json rename to tests/data/atom_reachability/python/atom_reachability.python.reachables.slices.json diff --git a/tests/atom_reachability/python/atom_reachability.python.usages.slices.json b/tests/data/atom_reachability/python/atom_reachability.python.usages.slices.json similarity index 100% rename from tests/atom_reachability/python/atom_reachability.python.usages.slices.json rename to tests/data/atom_reachability/python/atom_reachability.python.usages.slices.json From 9efd87d769ae331be1c5bad90d1133238e32e82d Mon Sep 17 00:00:00 2001 From: ziad hany Date: Wed, 28 Oct 2026 20:50:21 +0300 Subject: [PATCH 4/9] Update the docs Signed-off-by: ziadhany --- ...torial-web-ui-atom-symbol-reachability.png | Bin 0 -> 185064 bytes docs/source/index.rst | 2 +- docs/source/skeleton-usage.rst | 160 ------------------ ...al_atom_reachability_integration_tools.rst | 0 4 files changed, 1 insertion(+), 161 deletions(-) create mode 100644 docs/source/images/tutorial-web-ui-atom-symbol-reachability.png delete mode 100644 docs/source/skeleton-usage.rst rename docs/source/{contribute => }/tutorial_atom_reachability_integration_tools.rst (100%) diff --git a/docs/source/images/tutorial-web-ui-atom-symbol-reachability.png b/docs/source/images/tutorial-web-ui-atom-symbol-reachability.png new file mode 100644 index 0000000000000000000000000000000000000000..3cd9119e216d594fc52429257e02f17362b91b15 GIT binary patch literal 185064 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

pG%F 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

DYkSN%#i~MK9NqK&p_u$9w5*G%( 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

7jUVCViJ&84%rGj=9&DVCz`OnGgX@>xrnjT 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 literal 0 HcmV?d00001 diff --git a/docs/source/index.rst b/docs/source/index.rst index eb63717..42b479a 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -5,7 +5,7 @@ Welcome to nexb-skeleton's documentation! :maxdepth: 2 :caption: Contents: - skeleton-usage + tutorial_atom_reachability_integration_tools contribute/contrib_doc Indices and tables diff --git a/docs/source/skeleton-usage.rst b/docs/source/skeleton-usage.rst deleted file mode 100644 index 6cb4cc5..0000000 --- a/docs/source/skeleton-usage.rst +++ /dev/null @@ -1,160 +0,0 @@ -Usage -===== -A brand new project -------------------- -.. code-block:: bash - - git init my-new-repo - cd my-new-repo - git pull git@github.com:nexB/skeleton - - # Create the new repo on GitHub, then update your remote - git remote set-url origin git@github.com:nexB/your-new-repo.git - -From here, you can make the appropriate changes to the files for your specific project. - -Update an existing project ---------------------------- -.. code-block:: bash - - cd my-existing-project - git remote add skeleton git@github.com:nexB/skeleton - git fetch skeleton - git merge skeleton/main --allow-unrelated-histories - -This is also the workflow to use when updating the skeleton files in any given repository. - -Customizing ------------ - -You typically want to perform these customizations: - -- remove or update the src/README.rst and tests/README.rst files -- set project info and dependencies in setup.cfg -- check the configure and configure.bat defaults - -Initializing a project ----------------------- - -All projects using the skeleton will be expected to pull all of it dependencies -from thirdparty.aboutcode.org/pypi or the local thirdparty directory, using -requirements.txt and/or requirements-dev.txt to determine what version of a -package to collect. By default, PyPI will not be used to find and collect -packages from. - -In the case where we are starting a new project where we do not have -requirements.txt and requirements-dev.txt and whose dependencies are not yet on -thirdparty.aboutcode.org/pypi, we run the following command after adding and -customizing the skeleton files to your project: - -.. code-block:: bash - - ./configure - -This will initialize the virtual environment for the project, pull in the -dependencies from PyPI and add them to the virtual environment. - - -Generating requirements.txt and requirements-dev.txt ----------------------------------------------------- - -After the project has been initialized, we can generate the requirements.txt and -requirements-dev.txt files. - -Ensure the virtual environment is enabled. - -.. code-block:: bash - - source venv/bin/activate - -To generate requirements.txt: - -.. code-block:: bash - - python etc/scripts/gen_requirements.py -s venv/lib/python/site-packages/ - -Replace \ with the version number of the Python being used, for example: -``venv/lib/python3.6/site-packages/`` - -To generate requirements-dev.txt after requirements.txt has been generated: - -.. code-block:: bash - - ./configure --dev - python etc/scripts/gen_requirements_dev.py -s venv/lib/python/site-packages/ - -Note: on Windows, the ``site-packages`` directory is located at ``venv\Lib\site-packages\`` - -.. code-block:: bash - - python .\\etc\\scripts\\gen_requirements.py -s .\\venv\\Lib\\site-packages\\ - .\configure --dev - python .\\etc\\scripts\\gen_requirements_dev.py -s .\\venv\\Lib\\site-packages\\ - - -Collecting and generating ABOUT files for dependencies ------------------------------------------------------- - -Ensure that the dependencies used by ``etc/scripts/fetch_thirdparty.py`` are installed: - -.. code-block:: bash - - pip install -r etc/scripts/requirements.txt - -Once we have requirements.txt and requirements-dev.txt, we can fetch the project -dependencies as wheels and generate ABOUT files for them: - -.. code-block:: bash - - python etc/scripts/fetch_thirdparty.py -r requirements.txt -r requirements-dev.txt - -There may be issues with the generated ABOUT files, which will have to be -corrected. You can check to see if your corrections are valid by running: - -.. code-block:: bash - - python etc/scripts/check_thirdparty.py -d thirdparty - -Once the wheels are collected and the ABOUT files are generated and correct, -upload them to thirdparty.aboutcode.org/pypi by placing the wheels and ABOUT -files from the thirdparty directory to the pypi directory at -https://github.com/aboutcode-org/thirdparty-packages - - -Usage after project initialization ----------------------------------- - -Once the ``requirements.txt`` and ``requirements-dev.txt`` have been generated -and the project dependencies and their ABOUT files have been uploaded to -thirdparty.aboutcode.org/pypi, you can configure the project as needed, typically -when you update dependencies or use a new checkout. - -If the virtual env for the project becomes polluted, or you would like to remove -it, use the ``--clean`` option: - -.. code-block:: bash - - ./configure --clean - -Then you can run ``./configure`` again to set up the project virtual environment. - -To set up the project for development use: - -.. code-block:: bash - - ./configure --dev - -To update the project dependencies (adding, removing, updating packages, etc.), -update the dependencies in ``setup.cfg``, then run: - -.. code-block:: bash - - ./configure --clean # Remove existing virtual environment - source venv/bin/activate # Ensure virtual environment is activated - python etc/scripts/gen_requirements.py -s venv/lib/python/site-packages/ # Regenerate requirements.txt - python etc/scripts/gen_requirements_dev.py -s venv/lib/python/site-packages/ # Regenerate requirements-dev.txt - pip install -r etc/scripts/requirements.txt # Install dependencies needed by etc/scripts/bootstrap.py - python etc/scripts/fetch_thirdparty.py -r requirements.txt -r requirements-dev.txt # Collect dependency wheels and their ABOUT files - -Ensure that the generated ABOUT files are valid, then take the dependency wheels -and ABOUT files and upload them to thirdparty.aboutcode.org/pypi. diff --git a/docs/source/contribute/tutorial_atom_reachability_integration_tools.rst b/docs/source/tutorial_atom_reachability_integration_tools.rst similarity index 100% rename from docs/source/contribute/tutorial_atom_reachability_integration_tools.rst rename to docs/source/tutorial_atom_reachability_integration_tools.rst From 27865aa56777f632f8ae0e2cd8e68a2eedf2893f Mon Sep 17 00:00:00 2001 From: ziad hany Date: Wed, 28 Oct 2026 21:03:49 +0300 Subject: [PATCH 5/9] Update the docs Fix docker compose with atom Signed-off-by: ziadhany --- README.rst | 3 ++- docker-compose.rechable.yml => docker-compose.yml | 2 +- docker.env | 10 ++++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) rename docker-compose.rechable.yml => docker-compose.yml (99%) create mode 100644 docker.env diff --git a/README.rst b/README.rst index b839e22..af3e04c 100644 --- a/README.rst +++ b/README.rst @@ -3,7 +3,8 @@ reachablecode ================= reachablecode is a collection of pipelines for analyzing code reachability. -It integrates with ScanCode.io as a pipeline plugin and uses tools like (Atom, CPG, .. ) to determine whether +It integrates with ScanCode.io as a pipeline plugin +and uses tools like (Atom, CPG, .. ) to determine whether vulnerable code symbols are reachable within a project's codebase. Other information: diff --git a/docker-compose.rechable.yml b/docker-compose.yml similarity index 99% rename from docker-compose.rechable.yml rename to docker-compose.yml index 9a31da3..3debc19 100644 --- a/docker-compose.rechable.yml +++ b/docker-compose.yml @@ -113,7 +113,7 @@ services: atom: profiles: - - atom + - atom build: context: . dockerfile: atom.Dockerfile diff --git a/docker.env b/docker.env new file mode 100644 index 0000000..48d19ef --- /dev/null +++ b/docker.env @@ -0,0 +1,10 @@ +POSTGRES_DB=scancodeio +POSTGRES_USER=scancodeio +POSTGRES_PASSWORD=scancodeio +POSTGRES_INITDB_ARGS=--encoding=UTF-8 --lc-collate=en_US.UTF-8 --lc-ctype=en_US.UTF-8 + +SCANCODEIO_DB_HOST=db +SCANCODEIO_RQ_REDIS_HOST=redis +SCANCODEIO_ASYNC=True +SCANCODEIO_WORKSPACE_LOCATION=/var/scancodeio/workspace/ +ATOM_EXECUTABLE=/atom-root/bin/atom \ No newline at end of file From 7042c07e4540c4033207620cd431621c77458bf7 Mon Sep 17 00:00:00 2001 From: ziad hany Date: Wed, 28 Oct 2026 21:09:18 +0300 Subject: [PATCH 6/9] Fix docker compose with atom Fix README.md Signed-off-by: ziadhany --- README.rst | 98 +--------------------------------------------- docker-compose.yml | 4 +- 2 files changed, 3 insertions(+), 99 deletions(-) diff --git a/README.rst b/README.rst index af3e04c..aba1d2c 100644 --- a/README.rst +++ b/README.rst @@ -46,100 +46,4 @@ Development Testing: -- To run tests: ``pytest -vvs`` - - -Acknowledgements, Funding, Support and Sponsoring --------------------------------------------------------- - -This project is funded, supported and sponsored by: - -- Generous support and contributions from users like you! -- the European Commission NGI programme -- the NLnet Foundation -- the Swiss State Secretariat for Education, Research and Innovation (SERI) -- Google, including the Google Summer of Code and the Google Seasons of Doc programmes -- Mercedes-Benz Group -- Microsoft and Microsoft Azure -- AboutCode ASBL -- nexB Inc. - - - -|europa| |dgconnect| - -|ngi| |nlnet| - -|aboutcode| |nexb| - - -This project was funded through the NGI0 Core Fund, a fund established by NLnet with financial -support from the European Commission's Next Generation Internet programme, under the aegis of DG -Communications Networks, Content and Technology under grant agreement No 101092990. - -|ngizerocore| https://nlnet.nl/project/Back2source-next/ - -.. |nlnet| image:: https://nlnet.nl/logo/banner.png - :target: https://nlnet.nl - :height: 50 - :alt: NLnet foundation logo - -.. |ngi| image:: https://ngi.eu/wp-content/uploads/thegem-logos/logo_8269bc6efcf731d34b6385775d76511d_1x.png - :target: https://ngi.eu35 - :height: 50 - :alt: NGI logo - -.. |nexb| image:: https://nexb.com/wp-content/uploads/2022/04/nexB.svg - :target: https://nexb.com - :height: 30 - :alt: nexB logo - -.. |europa| image:: https://ngi.eu/wp-content/uploads/sites/77/2017/10/bandiera_stelle.png - :target: http://ec.europa.eu/index_en.htm - :height: 40 - :alt: Europa logo - -.. |aboutcode| image:: https://aboutcode.org/wp-content/uploads/2023/10/AboutCode.svg - :target: https://aboutcode.org/ - :height: 30 - :alt: AboutCode logo - -.. |swiss| image:: https://www.sbfi.admin.ch/sbfi/en/_jcr_content/logo/image.imagespooler.png/1493119032540/logo.png - :target: https://www.sbfi.admin.ch/sbfi/en/home/seri/seri.html - :height: 40 - :alt: Swiss logo - -.. |dgconnect| image:: https://commission.europa.eu/themes/contrib/oe_theme/dist/ec/images/logo/positive/logo-ec--en.svg - :target: https://commission.europa.eu/about-european-commission/departments-and-executive-agencies/communications-networks-content-and-technology_en - :height: 40 - :alt: EC DG Connect logo - -.. |ngizerocore| image:: https://nlnet.nl/image/logos/NGI0_tag.svg - :target: https://nlnet.nl/core - :height: 40 - :alt: NGI Zero Core Logo - -.. |ngizerocommons| image:: https://nlnet.nl/image/logos/NGI0_tag.svg - :target: https://nlnet.nl/commonsfund/ - :height: 40 - :alt: NGI Zero Commons Logo - -.. |ngizeropet| image:: https://nlnet.nl/image/logos/NGI0PET_tag.svg - :target: https://nlnet.nl/PET - :height: 40 - :alt: NGI Zero PET logo - -.. |ngizeroentrust| image:: https://nlnet.nl/image/logos/NGI0Entrust_tag.svg - :target: https://nlnet.nl/entrust - :height: 38 - :alt: NGI Zero Entrust logo - -.. |ngiassure| image:: https://nlnet.nl/image/logos/NGIAssure_tag.svg - :target: https://nlnet.nl/image/logos/NGIAssure_tag.svg - :height: 32 - :alt: NGI Assure logo - -.. |ngidiscovery| image:: https://nlnet.nl/image/logos/NGI0Discovery_tag.svg - :target: https://nlnet.nl/discovery/ - :height: 40 - :alt: NGI Discovery logo +- To run tests: ``pytest -vvs`` \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 3debc19..b2bbe2d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -33,7 +33,7 @@ services: web: build: - context: . + context: ../scancode.io command: sh -c " ./manage.py migrate && ./manage.py collectstatic --no-input --verbosity 0 --clear && @@ -59,7 +59,7 @@ services: worker: build: - context: . + context: ../scancode.io # Ensure that potential db migrations run first by waiting until "web" is up command: wait-for-it --strict --timeout=600 web:8000 -- sh -c " ./manage.py rqworker --worker-class scancodeio.worker.ScanCodeIOWorker From 40c5fd39a31b8bbea84713aad92bdb8386886cf3 Mon Sep 17 00:00:00 2001 From: ziad hany Date: Wed, 28 Oct 2026 21:11:56 +0300 Subject: [PATCH 7/9] Add a newline at end of README.rst Signed-off-by: ziadhany --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index aba1d2c..b5b4f05 100644 --- a/README.rst +++ b/README.rst @@ -46,4 +46,4 @@ Development Testing: -- To run tests: ``pytest -vvs`` \ No newline at end of file +- To run tests: ``pytest -vvs`` From 9b4c3000fddacead731c9a0b0591a9257d3d5411 Mon Sep 17 00:00:00 2001 From: ziad hany Date: Wed, 28 Oct 2026 21:52:51 +0300 Subject: [PATCH 8/9] Fix a typo in the docs Signed-off-by: ziadhany --- README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index b5b4f05..f6b1940 100644 --- a/README.rst +++ b/README.rst @@ -1,8 +1,8 @@ ================= -reachablecode +ReachableCode ================= -reachablecode is a collection of pipelines for analyzing code reachability. +ReachableCode is a collection of pipelines for analyzing code reachability. It integrates with ScanCode.io as a pipeline plugin and uses tools like (Atom, CPG, .. ) to determine whether vulnerable code symbols are reachable within a project's codebase. @@ -36,7 +36,7 @@ To install: - Run ``pip install --editable ../reachablecode`` - Run ScanCode.io with ``./manage.py runserver --insecure`` and open the URL in your browser. - There is a new "android_d2d" pipeline available when creating a new project. + There is a new "analyze_atom_reachability" pipeline available when creating a new project. Development ---------------- From ef067418e7111311b69f5e84f000ac93d89c543d Mon Sep 17 00:00:00 2001 From: ziad hany Date: Wed, 28 Oct 2026 22:06:34 +0300 Subject: [PATCH 9/9] Add docs to the README.rst add version and remove fallback_version Signed-off-by: ziadhany --- README.rst | 2 +- pyproject.toml | 5 ----- setup.cfg | 1 + 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index f6b1940..53b3976 100644 --- a/README.rst +++ b/README.rst @@ -12,7 +12,7 @@ Other information: - License: Apache-2.0 - Copyright (c) nexB Inc., AboutCode.org and others. - Homepage: https://github.com/aboutcode-org/reachablecode/ - ++ Documentation: https://reachablecode.readthedocs.io/ Requirements ------------ diff --git a/pyproject.toml b/pyproject.toml index 3d500a0..ed5c8f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,11 +2,6 @@ requires = ["setuptools >= 50", "wheel"] build-backend = "setuptools.build_meta" -[tool.setuptools_scm] -# this is used populated when creating a git archive -# and when there is .git dir and/or there is no git installed -fallback_version = "9999.$Format:%h-%cs$" - [tool.pytest.ini_options] norecursedirs = [ ".git", diff --git a/setup.cfg b/setup.cfg index 4aa369a..443a4c7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,6 @@ [metadata] name = reachablecode +version = 0.0.1 license = Apache-2.0 # description must be on ONE line https://github.com/pypa/setuptools/issues/1390