Skip to content

How It Works

PCART Bot edited this page Sep 17, 2026 · 7 revisions

How PCART Works

This page explains PCART's internal mechanisms: the pipeline, workspace layout, pkl lifecycle, matching strategy, and data flow.

Pipeline Overview

Source Code ──► Preprocess ──► API Extract ──► API Map ──► Compatibility Analyze ──► Auto Repair ──► Report
                  │                │              │              │                      │
                  ▼                ▼              ▼              ▼                      ▼
             Code flattening   AST-based     Dynamic+Static   Change type          AST-level fix
             + instrumentation call discovery  signature match  detection          + dynamic/static validate

Step-by-step

  1. Preprocess: Flattens control flow (list/dict comprehensions, conditional returns), converts tabs to spaces, merges multi-line calls into single lines, then instruments the code with recordValue.py to capture runtime API call data.
  2. API Extract: Discovers calls to the target library and produces structured CallsiteRecord dictionaries keyed by artifact id. If optional PCResolve is installed, PCART uses its cross-file symbol tracing; otherwise it uses the built-in AST extractor, which resolves import aliases, assignment chains (a = X(); a.f()), and with/async with context managers.
  3. API Map: For callsites covered by the configured run, matches each call to its library definition — first via dynamic matching, then falling back to static matching if runtime signature inspection fails. Discovered but unexecuted callsites are reported as not covered, and compatibility analysis is skipped.
  4. Compatibility Analyze: Compares current vs target API signatures to detect parameter changes (addition, removal, renaming, reordering, type changes, positional↔keyword conversions).
  5. Auto Repair: Attempts AST-level fixes for incompatible calls, then validates fixes by running the repaired code in the target environment if parameter values can be recovered from pkl files, otherwise by static signature validation.
  6. Report: Generates an internal report and, with --write-patch, optional repair artifacts inside the workspace, then exports them to Report/runs/{run_id}/{command_id}/ for user access.

Workspace Layout

Each PCART run creates an isolated workspace under PCARTRuns/runs/:

PCARTRuns/runs/
└── {project}__{timestamp}-{NNN}/       # run_id
    └── cmd-001/                         # command_id
        ├── Copy/                        # Instrumented project copy for pkl generation
        │   ├── {projName}/              #   Project copy
        │   └── pkl/                     #   Runtime pkl files and manifests
        ├── Dynamic/                     # Stripped project copy for dynamic matching scripts
        │   └── {projName}/
        ├── data/                        # Intermediate JSON (call dicts, match snapshots)
        ├── temp/                        # Temporary files
        ├── Report/                      # Internal report (exported to Report/runs/...)
        └── metadata.json                # Run configuration metadata

User-visible reports are exported to:

Report/runs/
└── {run_id}/
    └── {command_id}/
        ├── {project}.txt                # Repair report
        ├── {project}_fixed_log.txt      # Error log, when errors are recorded
        ├── patches/                     # Optional source patches
        │   ├── successful.patch         # Successful repair results
        │   ├── unknown.patch            # Candidates requiring manual review
        │   └── failed.patch             # Candidates that failed repair validation
        └── fixed_project/
            └── {project}/               # Original project copy with Successful edits only

Patch files and the repaired project copy are generated only with --write-patch and applicable edits. The patches/ and fixed_project/ directories may be empty. See Optional Repair Patch Output below for details.

The Copy/bak_<projName>/ backup still exists inside the workspace for single-callsite re-instrumentation during target-environment pkl regeneration.

By default, intermediate workspaces remain available for diagnosis. Passing --clean-workspace removes only a successful run workspace after report export; failed workspaces and exported outputs under Report/runs/ are retained.

Optional Repair Patch Output

Patch output is disabled by default. Passing --write-patch uses existing repair results to generate source patches and a repaired project copy without changing repair logic or the text report. The original project is not modified.

  • successful.patch, unknown.patch, and failed.patch group applicable edits by repair status. Each patch independently compares its category's edits with the original source; the patches are not based on one another.
  • Unknown and Failed patches are candidates for manual review. Their edits are not automatically applied to fixed_project/{project}/; only Successful edits enter that copy of the original project.
  • Only calls identified as incompatible with a single, unambiguous repair expression that changes the call are collected for patch output. Report entries do not necessarily produce patch edits.
  • A category with no applicable edits produces no patch file. The repaired project copy is generated only when at least one Successful edit can be applied.
  • Edits replace the original callsite spans rather than regenerating whole files from the AST. Source bytes outside those spans, including comments, blank lines, UTF-8 BOM, and line endings, are preserved. Formatting inside a replaced call follows the repair expression.
  • Unsafe edits, such as mismatched or overlapping source spans or comments inside a call span that replacement would remove, are skipped and logged in {project}_fixed_log.txt. Edited source files must be UTF-8, optionally with a BOM.

Successful is a call-level repair validation result, not a guarantee that the entire repaired project runs successfully in targetEnv. Validate the project separately before adopting the repairs.

--write-patch can be combined with --clean-workspace. Exported patches and repaired project copies remain under Report/runs/{run_id}/{command_id}/ after successful workspace cleanup.

pkl File Lifecycle

pkl (pickle) files are the core data artifact that carries runtime API call information between pipeline stages.

Generation (recordValue.py)

When the instrumented project runs in the currentEnv:

  1. paraValueDict collects each API call's receiver object and parameter values at runtime.
  2. apiCoveredSet records which callsites were actually executed.
  3. callsiteInfoDict records structured callsite metadata for each instrumented call.
  4. At process exit (atexit), savePkls() writes pkl files to the workspace's Copy/pkl/.

Naming

pkl files are named by the structured artifact id produced by CallsiteIdentity.artifactId(), which uniquely identifies each callsite:

{rel_path_slug}__L{lineno}C{col_offset}__{call_slug}__{SHA256_hash}.pkl

Example: src_main_py__L42C5__torch_tensor__a1b2c3d4...64hex.pkl

The artifact id combines:

  • File slug: project-relative path, normalized (64 char max)
  • Position: L{lineno}C{col_offset} for precise source location
  • Call slug: normalized API name before the first ( (48 char max)
  • Hash: SHA256 of the full callsite payload for global uniqueness

When PCART can preserve both a runtime receiver and a reconstructable receiver expression, it generates two candidate pkl files. This applies to with/async with aliases and to supported assigned or self receivers whose source expression can be restored:

Suffix Content
__object.pkl Receiver captured as a runtime object via dill
__expr.pkl Receiver captured as a string expression (fallback)

The dual-candidate approach handles cases where the runtime object cannot be pickled (e.g., locked resources or C extension objects). Other callsites retain a single legacy-form candidate.

Manifest

Each callsite also produces a .manifest.json file tracking candidate statuses:

{
  "callsite": "src_main_py__L42C5__client_publish__a1b2c3d4...64hex",
  "covered": true,
  "candidates": [
    {"kind": "object", "status": "saved", "pkl": "src_main_py__L42C5__client_publish__a1b2...__object.pkl"},
    {"kind": "expr",  "status": "saved", "pkl": "src_main_py__L42C5__client_publish__a1b2...__expr.pkl"}
  ]
}

Candidates marked save_failed are re-generated in the target environment if needed.

Regeneration in Target Environment

If a pkl from currentEnv cannot be loaded in targetEnv (due to serialization format changes), PCART automatically:

  1. Re-instruments the source file for the failed callsite only.
  2. Re-runs the project in targetEnv.
  3. Saves the new pkl with a new_ prefix (e.g., new_src_main_py__L42C5__client_publish__a1b2...__object.pkl).

Consumption

  • Dynamic matching (dynamicMatch.py): Loads the pkl, reconstructs the API callable, and uses inspect.signature() to extract the parameter signature.
  • Value addition (addValueForAPI.py): Loads the pkl to fill in concrete parameter values for repair validation.
  • Repair validation (verifySingle.py): Loads the pkl and attempts to call the repaired API with recovered values.

Candidate Order

For the original data generated in currentEnv, PCART tries candidates in this order:

  1. <artifact_id>__object.pkl — runtime receiver
  2. <artifact_id>__expr.pkl — reconstructed-expression fallback
  3. <artifact_id>.pkl — single-candidate legacy form

When target-environment regeneration is triggered, newly generated candidates are then tried in the same object, expression, and legacy order with a new_ prefix. Regeneration is conditional; a missing pkl by itself does not always cause PCART to rerun the project.

Dynamic vs Static Matching

PCART uses a two-tier matching strategy for callsites recorded in the runtime coverage set. A statically discovered callsite that was not executed is reported as Coverage: No; it does not enter either signature-matching tier and no compatibility result is produced for it.

Dynamic Matching (preferred)

  1. Load the pkl file for the callsite.
  2. Reconstruct the API callable using the runtime receiver object and parameter values.
  3. Call inspect.signature() to get the actual runtime signature.
  4. Record the internal file path (inspect.getfile()) for disambiguation.

Falls back to static when:

  • Runtime data was recorded but no usable pkl candidate can be loaded.
  • inspect.signature() returns nullptr for a built-in, C extension, or otherwise non-inspectable API.
  • Target-environment pkl regeneration is unavailable or unsuccessful.

Static Matching (fallback)

  1. Match the API call name against the pre-extracted library API definitions from LibAPIExtraction/.
  2. Prefer an exact public-path match when one is available; otherwise use fuzzy name matching and qualified-name alignment.
  3. Preserve public re-export paths found through package imports and assignments when library definitions are extracted.
  4. Distinguish between .pyi-declared (built-in) APIs and .py-declared APIs.

Mixed Dynamic/Static Results

The current and target versions can use different matching methods. When one side is dynamic and the other is static:

  • A single exact static match for the restored public API path is compared directly with the dynamic signature.
  • A fuzzy static result is compared only when its qualified internal name aligns with the dynamic result.
  • If the results cannot be aligned safely, compatibility is reported as Unknown rather than comparing unrelated signatures.

Optional PCResolve Integration

PCResolve is not a configuration field or required dependency. If a compatible pcresolve package is installed in the PCART tool environment, PCART analyzes the project once and reuses its cross-file callsite lookup during preprocessing and compatibility analysis. If it is unavailable, PCART silently falls back to the built-in extractor.

withitem / async with Support

PCART detects API calls through with and async with context managers:

async with aiofiles.open("file.txt") as f:   # ← withitem: aiofiles.open("file.txt")
    await f.read()                            # ← alias "f" → aiofiles.open("file.txt").read()

How it works:

  1. Extraction (WithVisitor): Records each withitem's context_expr (the API call) and optional_vars (the alias), along with the line number range of the with block.
  2. Alias resolution (modifyWithName): When a call uses the alias (e.g., f.read()), it is recursively resolved to the full API path (e.g., aiofiles.open("file.txt").read()). For nested with blocks with same-named aliases, the innermost scope takes precedence.
  3. Receiver capture (recordValue.py): For withitem callers, both the runtime object and the string expression are saved as separate pkl candidates, since the runtime object may not survive serialization.

callsite-based Identification

To distinguish the same API called at different source locations, PCART uses a structured CallsiteIdentity to generate a stable, readable artifact id:

@dataclass(frozen=True)
class CallsiteIdentity:
    rel_path: str         # project-relative file path
    lineno: int           # line number
    col_offset: int       # column offset
    end_lineno: int       # end line number
    end_col_offset: int   # end column offset
    call_text: str        # original call text (for report display)
    normalized_call: str  # whitespace/quote-normalized call text (for identity)

The artifactId() method produces a stable key in the form:

{rel_slug}__L{lineno}C{col_offset}__{call_slug}__{SHA256_hash}

The CallsiteRecord separates this identity into three concerns:

  • artifact_id: Used for pkl/json/manifest/shared dictionary keys throughout the pipeline
  • call_text: Original call text for report display
  • format_api: Restored API path for static fuzzy matching

This key is used consistently throughout the pipeline:

  • Preprocess: As the dictionary key in paraValueDict and callsiteInfoDict instrumentation.
  • pkl naming: As the base for pkl file names.
  • Dynamic matching: As the lookup key to find the correct pkl.
  • Shared dictionary: As the cache key to avoid redundant matching across files.

Without callsite-based identification, two calls to the same API at different lines would share one pkl and one match result, causing incorrect signature assignments.

Related Pages