a3-python 0.1.19__tar.gz → 0.1.21__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {a3_python-0.1.19 → a3_python-0.1.21}/PKG-INFO +1 -1
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/analyzer.py +30 -15
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/ci/templates/a3-pr-scan.yml +1 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/ci/templates/a3-scheduled-scan.yml +1 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/semantics/symbolic_vm.py +79 -70
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/z3model/values.py +90 -14
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python.egg-info/PKG-INFO +1 -1
- {a3_python-0.1.19 → a3_python-0.1.21}/pyproject.toml +1 -1
- {a3_python-0.1.19 → a3_python-0.1.21}/MANIFEST.in +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/README.md +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/__init__.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/__main__.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/__init__.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/abstraction.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/advanced.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/assume_guarantee.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/bayesian_fp_scorer.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/boolean_programs.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/cegar_refinement.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/cegis.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/certificate_core.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/context_aware_verification.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/deep_barrier_theory.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/dsos_sdsos.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/enhanced_barrier_theory.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/extreme_verification.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/fast_barrier_filters.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/foundations.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/guard_to_barrier.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/houdini.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/hscc2004.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/hybrid_barrier.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/ic3_pdr.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/ice.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/ice_learning.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/impact_lazy.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/int_bmc.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/interpolation_imc.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/invariants.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/kitchensink_taxonomy.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/lasserre_hierarchy.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/learned_invariants.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/learning.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/papers_11_to_15_complete.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/papers_16_to_20_complete.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/papers_1_to_5_complete.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/papers_6_to_10_complete.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/parrilo_sos_sdp.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/path_validation.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/pdr_spacer.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/positivstellensatz.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/predicate_abstraction.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/program_analysis.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/quick_precheck.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/ranking.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/ranking_synthesis.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/sos_safety.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/sos_toolbox.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/sos_unified.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/sostools.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/spacer_chc.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/sparse_sos.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/step_relation.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/stochastic_barrier.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/sygus_synthesis.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/synthesis.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/synthesis_engine.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/templates.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/type_inference_verification.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/barriers/unified_sota_912.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/cfg/__init__.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/cfg/affine_loop_model.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/cfg/call_graph.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/cfg/control_flow.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/cfg/dataflow.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/cfg/loop_analysis.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/ci/__init__.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/ci/agentic_triage.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/ci/baseline.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/ci/config.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/ci/init_cmd.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/ci/sarif.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/ci/triage.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/cli.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/confidence_interval.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/confidence_scoring.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/__init__.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/barriers/__init__.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/barriers/abstract_values.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/barriers/contracts.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/barriers/deferred.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/barriers/device_analyzer.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/barriers/intervals.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/barriers/torch/__init__.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/barriers/torch/accelerators.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/barriers/torch/amp.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/barriers/torch/autograd.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/barriers/torch/backends.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/barriers/torch/core.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/barriers/torch/cuda.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/barriers/torch/data.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/barriers/torch/distributed.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/barriers/torch/distributions.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/barriers/torch/experimental.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/barriers/torch/export_compile.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/barriers/torch/fft.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/barriers/torch/hub_package.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/barriers/torch/jit.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/barriers/torch/linalg.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/barriers/torch/nn_functional.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/barriers/torch/nn_modules.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/barriers/torch/onnx.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/barriers/torch/optim.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/barriers/torch/profiler.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/barriers/torch/quantization.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/barriers/torch/registry.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/barriers/torch/sparse.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/barriers/torch/special.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/barriers/torch/tensor.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/barriers/torch/utils.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/base.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/builtin_relations.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/checker.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/relations.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/schema.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/security.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/security_lattice.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/stdlib.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/stdlib_module_relations.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/stdlib_stubs.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/contracts/torch_contracts.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/dse/__init__.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/dse/concolic.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/dse/constraint_solver.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/dse/hybrid.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/dse/lockstep.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/dse/path_condition.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/dse/selective_concolic.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/dse/stochastic_replay.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/dse/value_flow.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/evaluation/__init__.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/evaluation/deduplication.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/evaluation/repo_list.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/evaluation/scanner.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/fp_context.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/frontend/__init__.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/frontend/entry_points.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/frontend/loader.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/semantics/__init__.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/semantics/ast_guard_analysis.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/semantics/bmc.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/semantics/bytecode_summaries.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/semantics/concrete_vm.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/semantics/crash_summaries.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/semantics/framework_mocks.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/semantics/intent_detector.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/semantics/interprocedural_barriers.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/semantics/interprocedural_bugs.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/semantics/interprocedural_guards.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/semantics/interprocedural_taint.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/semantics/intraprocedural_taint.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/semantics/invariant_integration.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/semantics/oracles.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/semantics/security_tracker.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/semantics/security_tracker_lattice.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/semantics/sota_interprocedural.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/semantics/sota_intraprocedural.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/semantics/state.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/semantics/summaries.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/semantics/termination_integration.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/stochastic_risk.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/__init__.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/assert_fail.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/bounds.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/collection_bugs.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/data_race.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/deadlock.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/div_zero.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/double_free.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/exception_bugs.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/fp_domain.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/info_leak.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/integer_overflow.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/iterator_invalid.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/memory_leak.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/non_termination.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/null_ptr.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/panic.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/registry.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/security/__init__.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/security/cleartext.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/security/code_injection.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/security/command_injection.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/security/config.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/security/crypto.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/security/deserialization.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/security/filesystem.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/security/injection.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/security/lattice_detectors.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/security/path_injection.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/security/regex.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/security/sql_injection.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/security/ssrf.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/security/webapp.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/security/xml.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/security/xss.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/security/xxe.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/send_sync.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/stack_overflow.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/timing_channel.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/type_confusion.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/uninit_memory.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/unsafe/use_after_free.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/z3model/__init__.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/z3model/heap.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/z3model/taint.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/z3model/taint_lattice.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python/z3model/type_tracking.py +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python.egg-info/SOURCES.txt +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python.egg-info/dependency_links.txt +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python.egg-info/entry_points.txt +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python.egg-info/requires.txt +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/a3_python.egg-info/top_level.txt +0 -0
- {a3_python-0.1.19 → a3_python-0.1.21}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: a3-python
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.21
|
|
4
4
|
Summary: Catch real Python bugs before production — 99%+ accuracy, Z3 symbolic execution, LLM-powered false-positive filtering, zero-config GitHub CI
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -2135,13 +2135,13 @@ class Analyzer:
|
|
|
2135
2135
|
var_names = list(loop.modified_variables)
|
|
2136
2136
|
|
|
2137
2137
|
# Generate sample positive/negative examples
|
|
2138
|
-
positive =
|
|
2139
|
-
negative =
|
|
2138
|
+
positive = []
|
|
2139
|
+
negative = []
|
|
2140
2140
|
implications = []
|
|
2141
2141
|
|
|
2142
2142
|
# Add simple examples (heuristic)
|
|
2143
|
-
positive.
|
|
2144
|
-
positive.
|
|
2143
|
+
positive.append({v: 0 for v in var_names})
|
|
2144
|
+
positive.append({v: 1 for v in var_names})
|
|
2145
2145
|
|
|
2146
2146
|
# Try ICE learning
|
|
2147
2147
|
result = learn_ice_invariant(
|
|
@@ -2912,13 +2912,19 @@ class Analyzer:
|
|
|
2912
2912
|
# Compile the module
|
|
2913
2913
|
module_code = compile(source, str(filepath), 'exec')
|
|
2914
2914
|
|
|
2915
|
-
# Search for the function
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
if const.
|
|
2919
|
-
|
|
2915
|
+
# Search recursively for the function (handles class methods too)
|
|
2916
|
+
def _find_code(code_obj, name):
|
|
2917
|
+
for const in code_obj.co_consts:
|
|
2918
|
+
if isinstance(const, types.CodeType):
|
|
2919
|
+
if const.co_name == name:
|
|
2920
|
+
return const
|
|
2921
|
+
# Recurse into nested code objects (e.g., class bodies)
|
|
2922
|
+
result = _find_code(const, name)
|
|
2923
|
+
if result is not None:
|
|
2924
|
+
return result
|
|
2925
|
+
return None
|
|
2920
2926
|
|
|
2921
|
-
return
|
|
2927
|
+
return _find_code(module_code, function_name)
|
|
2922
2928
|
except Exception as e:
|
|
2923
2929
|
if self.verbose:
|
|
2924
2930
|
print(f" Error extracting function {function_name}: {e}")
|
|
@@ -2974,8 +2980,9 @@ class Analyzer:
|
|
|
2974
2980
|
if isinstance(const, types.CodeType):
|
|
2975
2981
|
name = const.co_name
|
|
2976
2982
|
|
|
2977
|
-
# Skip module-level code, lambdas, and
|
|
2978
|
-
|
|
2983
|
+
# Skip module-level code, lambdas, comprehensions, and
|
|
2984
|
+
# compiler-generated annotation functions (Python 3.14+ PEP 649)
|
|
2985
|
+
if name in ('<module>', '<lambda>', '<listcomp>', '<dictcomp>', '<setcomp>', '<genexpr>', '__annotate__'):
|
|
2979
2986
|
continue
|
|
2980
2987
|
|
|
2981
2988
|
# Check if this is a class definition
|
|
@@ -3050,8 +3057,9 @@ class Analyzer:
|
|
|
3050
3057
|
all_functions = {}
|
|
3051
3058
|
for const in module_code.co_consts:
|
|
3052
3059
|
if isinstance(const, types.CodeType):
|
|
3053
|
-
# Skip module-level code and
|
|
3054
|
-
|
|
3060
|
+
# Skip module-level code, lambdas, and compiler-generated
|
|
3061
|
+
# annotation functions (Python 3.14+ PEP 649)
|
|
3062
|
+
if const.co_name in ('<module>', '<lambda>', '__annotate__'):
|
|
3055
3063
|
continue
|
|
3056
3064
|
# Skip class definitions (they have __classdict__ in cellvars)
|
|
3057
3065
|
if '__classdict__' in const.co_cellvars:
|
|
@@ -3061,7 +3069,14 @@ class Analyzer:
|
|
|
3061
3069
|
# Check if there's any executable code at module level beyond function definitions
|
|
3062
3070
|
import dis
|
|
3063
3071
|
has_executable_code = False
|
|
3064
|
-
|
|
3072
|
+
# Include annotation bookkeeping opcodes (Python 3.14 PEP 649) so that
|
|
3073
|
+
# annotation-only modules are still correctly classified as library files
|
|
3074
|
+
function_def_opcodes = {
|
|
3075
|
+
'MAKE_FUNCTION', 'STORE_NAME', 'STORE_GLOBAL', 'LOAD_CONST',
|
|
3076
|
+
'MAKE_CELL', 'BUILD_SET', 'SET_ADD', 'POP_TOP',
|
|
3077
|
+
'SET_FUNCTION_ATTRIBUTE', 'LOAD_SMALL_INT', 'LOAD_NAME',
|
|
3078
|
+
'PUSH_NULL', 'SETUP_ANNOTATIONS',
|
|
3079
|
+
}
|
|
3065
3080
|
|
|
3066
3081
|
for instr in dis.get_instructions(module_code):
|
|
3067
3082
|
# Skip RESUME and function definition opcodes
|
|
@@ -511,6 +511,12 @@ class SymbolicVM:
|
|
|
511
511
|
self.verbose = verbose
|
|
512
512
|
self.paths: List[SymbolicPath] = []
|
|
513
513
|
self._instruction_cache: Dict[int, Dict[int, dis.Instruction]] = {}
|
|
514
|
+
self._obj_id_counter = 0
|
|
515
|
+
|
|
516
|
+
def fresh_obj_id(self) -> int:
|
|
517
|
+
"""Return a fresh unique object ID for heap allocation."""
|
|
518
|
+
self._obj_id_counter += 1
|
|
519
|
+
return self._obj_id_counter
|
|
514
520
|
|
|
515
521
|
def _solver_maybe_sat(self) -> bool:
|
|
516
522
|
"""
|
|
@@ -772,7 +778,16 @@ class SymbolicVM:
|
|
|
772
778
|
print(f" Top of stack: {frame.operand_stack[-3:]}")
|
|
773
779
|
|
|
774
780
|
# Increment per-instruction step counter (k-step reachability index).
|
|
775
|
-
|
|
781
|
+
# Skip counting annotation bookkeeping instructions (Python 3.14 PEP 649)
|
|
782
|
+
# so that type annotations don't consume the exploration budget.
|
|
783
|
+
_annotation_bookkeeping = (
|
|
784
|
+
instruction.opname == 'SETUP_ANNOTATIONS'
|
|
785
|
+
or (instruction.opname == 'STORE_NAME' and instruction.argval in ('__annotate__', '__annotations__', '__conditional_annotations__'))
|
|
786
|
+
or (instruction.opname == 'LOAD_NAME' and instruction.argval == '__conditional_annotations__')
|
|
787
|
+
or (instruction.opname == 'SET_ADD' and hasattr(frame, 'locals') and '__conditional_annotations__' in getattr(frame, 'locals', {}))
|
|
788
|
+
)
|
|
789
|
+
if not _annotation_bookkeeping:
|
|
790
|
+
state.step_count += 1
|
|
776
791
|
|
|
777
792
|
self._execute_instruction(state, frame, instruction)
|
|
778
793
|
path.trace.append(f"{instruction.offset:4d}: {instruction.opname} {instruction.argrepr}")
|
|
@@ -1201,13 +1216,13 @@ class SymbolicVM:
|
|
|
1201
1216
|
"""Compute the next instruction offset."""
|
|
1202
1217
|
code = frame.code
|
|
1203
1218
|
|
|
1204
|
-
#
|
|
1205
|
-
# This
|
|
1219
|
+
# Use a SEPARATE cache from _instruction_cache (which stores offset→Instruction).
|
|
1220
|
+
# This cache stores offset→next_offset (int→int).
|
|
1206
1221
|
cache_key = id(code)
|
|
1207
|
-
if not hasattr(self, '
|
|
1208
|
-
self.
|
|
1222
|
+
if not hasattr(self, '_next_offset_cache'):
|
|
1223
|
+
self._next_offset_cache = {}
|
|
1209
1224
|
|
|
1210
|
-
if cache_key not in self.
|
|
1225
|
+
if cache_key not in self._next_offset_cache:
|
|
1211
1226
|
instructions = list(dis.get_instructions(code))
|
|
1212
1227
|
# Build offset -> next_offset map for O(1) lookup
|
|
1213
1228
|
offset_map = {}
|
|
@@ -1216,9 +1231,9 @@ class SymbolicVM:
|
|
|
1216
1231
|
offset_map[inst.offset] = instructions[i + 1].offset
|
|
1217
1232
|
else:
|
|
1218
1233
|
offset_map[inst.offset] = len(code.co_code)
|
|
1219
|
-
self.
|
|
1234
|
+
self._next_offset_cache[cache_key] = offset_map
|
|
1220
1235
|
|
|
1221
|
-
offset_map = self.
|
|
1236
|
+
offset_map = self._next_offset_cache[cache_key]
|
|
1222
1237
|
return offset_map.get(instr.offset, instr.offset + 2)
|
|
1223
1238
|
|
|
1224
1239
|
def _set_security_detection_flag(self, state: SymbolicMachineState, violation):
|
|
@@ -2744,8 +2759,9 @@ class SymbolicVM:
|
|
|
2744
2759
|
return
|
|
2745
2760
|
self.solver.pop()
|
|
2746
2761
|
|
|
2747
|
-
# Unpack elements and push onto stack
|
|
2748
|
-
|
|
2762
|
+
# Unpack elements and push onto stack in REVERSE order
|
|
2763
|
+
# so that TOS = first element (matches CPython semantics)
|
|
2764
|
+
for i in range(count - 1, -1, -1):
|
|
2749
2765
|
if i in seq_obj.elements:
|
|
2750
2766
|
frame.operand_stack.append(seq_obj.elements[i])
|
|
2751
2767
|
else:
|
|
@@ -2754,12 +2770,12 @@ class SymbolicVM:
|
|
|
2754
2770
|
frame.operand_stack.append(sym_elem)
|
|
2755
2771
|
else:
|
|
2756
2772
|
# Sequence object not found in heap - create symbolic unpacked values
|
|
2757
|
-
for i in range(count):
|
|
2773
|
+
for i in range(count - 1, -1, -1):
|
|
2758
2774
|
sym_elem = SymbolicValue(ValueTag.OBJ, z3.Int(f"unpack_unknown_{id(seq)}_{i}"))
|
|
2759
2775
|
frame.operand_stack.append(sym_elem)
|
|
2760
2776
|
else:
|
|
2761
2777
|
# Could not extract concrete obj_id - create symbolic unpacked values
|
|
2762
|
-
for i in range(count):
|
|
2778
|
+
for i in range(count - 1, -1, -1):
|
|
2763
2779
|
sym_elem = SymbolicValue(ValueTag.OBJ, z3.Int(f"unpack_symbolic_{id(seq)}_{i}"))
|
|
2764
2780
|
frame.operand_stack.append(sym_elem)
|
|
2765
2781
|
|
|
@@ -2767,15 +2783,16 @@ class SymbolicVM:
|
|
|
2767
2783
|
|
|
2768
2784
|
elif opname == "STORE_SUBSCR":
|
|
2769
2785
|
# STORE_SUBSCR: Implements container[index] = value
|
|
2770
|
-
#
|
|
2771
|
-
#
|
|
2786
|
+
# CPython stack layout: [..., value, container, index]
|
|
2787
|
+
# TOS=index, TOS1=container, TOS2=value
|
|
2788
|
+
# Pop order: index, container, value
|
|
2772
2789
|
if len(frame.operand_stack) < 3:
|
|
2773
2790
|
state.exception = "StackUnderflow"
|
|
2774
2791
|
return
|
|
2775
2792
|
|
|
2776
|
-
|
|
2777
|
-
container = frame.operand_stack.pop()
|
|
2778
|
-
|
|
2793
|
+
index = frame.operand_stack.pop() # TOS
|
|
2794
|
+
container = frame.operand_stack.pop() # TOS1
|
|
2795
|
+
value = frame.operand_stack.pop() # TOS2
|
|
2779
2796
|
|
|
2780
2797
|
# Check for None misuse on container
|
|
2781
2798
|
self.solver.push()
|
|
@@ -5515,11 +5532,16 @@ class SymbolicVM:
|
|
|
5515
5532
|
# argval is an index into a common constants table
|
|
5516
5533
|
const_repr = instr.argrepr
|
|
5517
5534
|
|
|
5518
|
-
# Common exception types
|
|
5535
|
+
# Common exception types (including NotImplementedError which is
|
|
5536
|
+
# raised by compiler-generated __annotate__ functions in Python 3.14+)
|
|
5519
5537
|
exception_types = ['AssertionError', 'RuntimeError', 'ValueError', 'TypeError',
|
|
5520
5538
|
'KeyError', 'IndexError', 'AttributeError', 'ImportError',
|
|
5521
5539
|
'NameError', 'OSError', 'IOError', 'ZeroDivisionError',
|
|
5522
5540
|
'StopIteration', 'StopAsyncIteration', 'SystemExit',
|
|
5541
|
+
'NotImplementedError', 'OverflowError', 'UnicodeError',
|
|
5542
|
+
'UnicodeDecodeError', 'UnicodeEncodeError',
|
|
5543
|
+
'FileNotFoundError', 'PermissionError', 'TimeoutError',
|
|
5544
|
+
'ConnectionError', 'BrokenPipeError',
|
|
5523
5545
|
'BaseException', 'Exception']
|
|
5524
5546
|
|
|
5525
5547
|
matched_exc = None
|
|
@@ -5913,66 +5935,52 @@ class SymbolicVM:
|
|
|
5913
5935
|
state.exception = "InfeasiblePath"
|
|
5914
5936
|
|
|
5915
5937
|
elif opname == "PUSH_EXC_INFO":
|
|
5916
|
-
#
|
|
5917
|
-
#
|
|
5918
|
-
# Stack layout: [exc_type, exc_value, exc_traceback, ...]
|
|
5938
|
+
# Python 3.11+: PUSH_EXC_INFO pushes (prev_exc, new_exc)
|
|
5939
|
+
# prev_exc is the previous exception (None if none), new_exc is the current one
|
|
5919
5940
|
if state.exception:
|
|
5920
|
-
#
|
|
5921
|
-
# Look up the exception type from builtins to get the correct payload
|
|
5941
|
+
# Create the exception value with proper type info
|
|
5922
5942
|
if state.exception in frame.builtins:
|
|
5923
|
-
|
|
5943
|
+
exc_val = frame.builtins[state.exception]
|
|
5924
5944
|
else:
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
exc_type_val._exception_type = state.exception
|
|
5945
|
+
exc_val = SymbolicValue(ValueTag.OBJ, z3.IntVal(-2))
|
|
5946
|
+
exc_val._exception_type = state.exception
|
|
5928
5947
|
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
frame.operand_stack.
|
|
5948
|
+
# Push prev_exc (simplified as None) and current exception
|
|
5949
|
+
prev_exc = SymbolicValue.none()
|
|
5950
|
+
frame.operand_stack.append(prev_exc) # prev_exc
|
|
5951
|
+
frame.operand_stack.append(exc_val) # current exc
|
|
5933
5952
|
frame.instruction_offset = self._next_offset(frame, instr)
|
|
5934
5953
|
|
|
5935
5954
|
elif opname == "CHECK_EXC_MATCH":
|
|
5936
|
-
#
|
|
5937
|
-
#
|
|
5938
|
-
#
|
|
5939
|
-
|
|
5955
|
+
# Python 3.11+: CHECK_EXC_MATCH (left, right -- left, bool)
|
|
5956
|
+
# TOS = match_type (exception class to compare against)
|
|
5957
|
+
# TOS1 = exc_value (stays on stack)
|
|
5958
|
+
# Result: pushes boolean indicating whether exc matches match_type
|
|
5959
|
+
if len(frame.operand_stack) < 2:
|
|
5940
5960
|
state.exception = "StackUnderflow"
|
|
5941
5961
|
return
|
|
5942
5962
|
|
|
5943
|
-
match_type = frame.operand_stack.pop()
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
#
|
|
5947
|
-
if
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
if exc_type.tag == ValueTag.OBJ and match_type.tag == ValueTag.OBJ:
|
|
5955
|
-
# Create symbolic comparison: exc_type.payload == match_type.payload
|
|
5956
|
-
matches_expr = exc_type.payload == match_type.payload
|
|
5957
|
-
match_result = SymbolicValue(ValueTag.BOOL, matches_expr)
|
|
5958
|
-
frame.operand_stack.append(match_result)
|
|
5959
|
-
else:
|
|
5960
|
-
# Unknown types, conservatively assume it could match
|
|
5961
|
-
frame.operand_stack.append(SymbolicValue.bool(True))
|
|
5963
|
+
match_type = frame.operand_stack.pop() # TOS = type to match
|
|
5964
|
+
exc_value = frame.operand_stack[-1] # TOS1 = stays on stack
|
|
5965
|
+
|
|
5966
|
+
# Compare exception types
|
|
5967
|
+
if hasattr(exc_value, '_exception_type') and hasattr(match_type, '_exception_type'):
|
|
5968
|
+
matches = exc_value._exception_type == match_type._exception_type
|
|
5969
|
+
frame.operand_stack.append(SymbolicValue.bool(matches))
|
|
5970
|
+
elif exc_value.tag == ValueTag.OBJ and match_type.tag == ValueTag.OBJ:
|
|
5971
|
+
matches_expr = exc_value.payload == match_type.payload
|
|
5972
|
+
match_result = SymbolicValue(ValueTag.BOOL, matches_expr)
|
|
5973
|
+
frame.operand_stack.append(match_result)
|
|
5962
5974
|
else:
|
|
5963
|
-
|
|
5964
|
-
|
|
5975
|
+
# Unknown types, conservatively assume it could match
|
|
5976
|
+
frame.operand_stack.append(SymbolicValue.bool(True))
|
|
5965
5977
|
|
|
5966
5978
|
frame.instruction_offset = self._next_offset(frame, instr)
|
|
5967
5979
|
|
|
5968
5980
|
elif opname == "POP_EXCEPT":
|
|
5969
|
-
#
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
if len(frame.operand_stack) >= 3:
|
|
5973
|
-
frame.operand_stack.pop() # exc_tb
|
|
5974
|
-
frame.operand_stack.pop() # exc_value
|
|
5975
|
-
frame.operand_stack.pop() # exc_type
|
|
5981
|
+
# Python 3.11+: POP_EXCEPT pops one value (the exception)
|
|
5982
|
+
if frame.operand_stack:
|
|
5983
|
+
frame.operand_stack.pop()
|
|
5976
5984
|
|
|
5977
5985
|
# Clear exception state
|
|
5978
5986
|
state.exception = None
|
|
@@ -6015,11 +6023,11 @@ class SymbolicVM:
|
|
|
6015
6023
|
elif var_index < num_varnames + num_cellvars:
|
|
6016
6024
|
# This is a cellvar (variable in this function that inner functions will reference)
|
|
6017
6025
|
cell_index = var_index - num_varnames
|
|
6018
|
-
frame.cells[cell_index] =
|
|
6026
|
+
frame.cells[cell_index] = SymbolicValue.none() # Initialize as empty cell
|
|
6019
6027
|
else:
|
|
6020
6028
|
# This is a freevar (variable from outer scope)
|
|
6021
6029
|
freevar_index = var_index - num_varnames - num_cellvars
|
|
6022
|
-
frame.freevars[freevar_index] =
|
|
6030
|
+
frame.freevars[freevar_index] = SymbolicValue.none() # Initialize as empty
|
|
6023
6031
|
|
|
6024
6032
|
frame.instruction_offset = self._next_offset(frame, instr)
|
|
6025
6033
|
|
|
@@ -6757,7 +6765,7 @@ class SymbolicVM:
|
|
|
6757
6765
|
|
|
6758
6766
|
# Convert to boolean using is_true helper
|
|
6759
6767
|
# is_true returns z3.ExprRef (BoolRef), we need to wrap it in a SymbolicValue
|
|
6760
|
-
bool_expr = is_true(val,
|
|
6768
|
+
bool_expr = is_true(val, self.solver)
|
|
6761
6769
|
bool_val = SymbolicValue(
|
|
6762
6770
|
ValueTag.BOOL,
|
|
6763
6771
|
z3.If(bool_expr, z3.IntVal(1), z3.IntVal(0))
|
|
@@ -6835,11 +6843,12 @@ class SymbolicVM:
|
|
|
6835
6843
|
state.exception = "StackUnderflow"
|
|
6836
6844
|
return
|
|
6837
6845
|
|
|
6838
|
-
|
|
6839
|
-
|
|
6846
|
+
func = frame.operand_stack.pop() # TOS = function
|
|
6847
|
+
attr = frame.operand_stack.pop() # TOS1 = attribute value (consumed)
|
|
6848
|
+
frame.operand_stack.append(func) # Push function back on stack
|
|
6840
6849
|
|
|
6841
6850
|
# For symbolic execution, we don't need to track these attributes yet
|
|
6842
|
-
# Just consume the value and keep the function
|
|
6851
|
+
# Just consume the attr value and keep the function
|
|
6843
6852
|
|
|
6844
6853
|
frame.instruction_offset = self._next_offset(frame, instr)
|
|
6845
6854
|
|
|
@@ -7822,7 +7831,7 @@ class SymbolicVM:
|
|
|
7822
7831
|
# Converts list to tuple (structural operation)
|
|
7823
7832
|
# Symbolically: create a fresh tuple object
|
|
7824
7833
|
tuple_id = z3.Int(f"intrinsic_tuple_{instr.offset}_{id(frame)}")
|
|
7825
|
-
tuple_obj = SymbolicValue(ValueTag.
|
|
7834
|
+
tuple_obj = SymbolicValue(ValueTag.TUPLE, tuple_id) # ✓ Correct: TUPLE not OBJ
|
|
7826
7835
|
frame.operand_stack.append(tuple_obj)
|
|
7827
7836
|
frame.instruction_offset = self._next_offset(frame, instr)
|
|
7828
7837
|
else:
|
|
@@ -131,7 +131,7 @@ class SymbolicValue:
|
|
|
131
131
|
sym = z3.Bool(name)
|
|
132
132
|
if solver:
|
|
133
133
|
solver.add(True)
|
|
134
|
-
return SymbolicValue.bool(
|
|
134
|
+
return SymbolicValue.bool(sym)
|
|
135
135
|
|
|
136
136
|
@staticmethod
|
|
137
137
|
def fresh_obj(name: str, solver: z3.Solver = None) -> 'SymbolicValue':
|
|
@@ -495,8 +495,23 @@ def binary_op_floordiv(left: SymbolicValue, right: SymbolicValue, solver: z3.Sol
|
|
|
495
495
|
z3.And(right.is_float(), right.as_float() == z3.RealVal(0))
|
|
496
496
|
)
|
|
497
497
|
|
|
498
|
-
# For int // int, use
|
|
499
|
-
|
|
498
|
+
# For int // int, use Python floor division.
|
|
499
|
+
# Z3's integer `/` is Euclidean division (remainder always non-negative),
|
|
500
|
+
# which differs from Python's floor division when the divisor is negative.
|
|
501
|
+
# Python: 7 // -2 = -4, but Z3: 7 / -2 = -3
|
|
502
|
+
# Fix: adjust when divisor is negative and there's a nonzero remainder.
|
|
503
|
+
# Guard: Z3's `%` requires IntSort, so only build this path for int payloads.
|
|
504
|
+
if z3.is_int(left.payload) and z3.is_int(right.payload):
|
|
505
|
+
euclid_div = left.as_int() / right.as_int()
|
|
506
|
+
euclid_mod = left.as_int() % right.as_int()
|
|
507
|
+
result_int = z3.If(
|
|
508
|
+
z3.And(right.as_int() < 0, euclid_mod != 0),
|
|
509
|
+
euclid_div - 1,
|
|
510
|
+
euclid_div
|
|
511
|
+
)
|
|
512
|
+
else:
|
|
513
|
+
# Float inputs: int path unused; dummy with correct sort for z3.If
|
|
514
|
+
result_int = z3.IntVal(0)
|
|
500
515
|
|
|
501
516
|
# Convert to float using Z3 sort checking
|
|
502
517
|
left_val = left.payload
|
|
@@ -512,7 +527,9 @@ def binary_op_floordiv(left: SymbolicValue, right: SymbolicValue, solver: z3.Sol
|
|
|
512
527
|
else:
|
|
513
528
|
right_as_float = right_val
|
|
514
529
|
|
|
515
|
-
|
|
530
|
+
# For float floor division, compute floor of the real quotient.
|
|
531
|
+
# z3.ToInt is floor for reals, then convert back to Real for float result.
|
|
532
|
+
result_float = z3.ToReal(z3.ToInt(left_as_float / right_as_float))
|
|
516
533
|
|
|
517
534
|
is_numeric_float = z3.Or(both_floats, int_float, float_int)
|
|
518
535
|
result_payload = z3.If(is_numeric_float, result_float, result_int)
|
|
@@ -552,8 +569,22 @@ def binary_op_mod(left: SymbolicValue, right: SymbolicValue, solver: z3.Solver)
|
|
|
552
569
|
z3.And(right.is_float(), right.as_float() == z3.RealVal(0))
|
|
553
570
|
)
|
|
554
571
|
|
|
555
|
-
# For int % int, use
|
|
556
|
-
|
|
572
|
+
# For int % int, use Python floor modulo.
|
|
573
|
+
# Z3's integer `%` is Euclidean mod (always non-negative),
|
|
574
|
+
# which differs from Python's floor mod when the divisor is negative.
|
|
575
|
+
# Python: 7 % -2 = -1, but Z3: 7 % -2 = 1
|
|
576
|
+
# Fix: adjust when divisor is negative and there's a nonzero remainder.
|
|
577
|
+
# Guard: Z3's `%` requires IntSort, so only build this path for int payloads.
|
|
578
|
+
if z3.is_int(left.payload) and z3.is_int(right.payload):
|
|
579
|
+
euclid_mod = left.as_int() % right.as_int()
|
|
580
|
+
result_int = z3.If(
|
|
581
|
+
z3.And(right.as_int() < 0, euclid_mod != 0),
|
|
582
|
+
euclid_mod + right.as_int(),
|
|
583
|
+
euclid_mod
|
|
584
|
+
)
|
|
585
|
+
else:
|
|
586
|
+
# Float inputs: int path unused; dummy with correct sort for z3.If
|
|
587
|
+
result_int = z3.IntVal(0)
|
|
557
588
|
|
|
558
589
|
# For float modulo, we approximate (Z3 doesn't have built-in real modulo)
|
|
559
590
|
# Convert to float using Z3 sort checking
|
|
@@ -759,16 +790,26 @@ def compare_op_lt(left: SymbolicValue, right: SymbolicValue, solver: z3.Solver)
|
|
|
759
790
|
"""Symbolic less-than comparison."""
|
|
760
791
|
# Accept OBJ types (conservative overapproximation for soundness)
|
|
761
792
|
# OBJ might be anything, including comparable types
|
|
793
|
+
# Also accept float and mixed int/float comparisons
|
|
762
794
|
type_ok = z3.Or(
|
|
763
795
|
z3.And(left.is_int(), right.is_int()),
|
|
796
|
+
z3.And(left.is_float(), right.is_float()),
|
|
797
|
+
z3.And(left.is_int(), right.is_float()),
|
|
798
|
+
z3.And(left.is_float(), right.is_int()),
|
|
764
799
|
left.is_obj(),
|
|
765
800
|
right.is_obj()
|
|
766
801
|
)
|
|
767
802
|
# When OBJ involved, return nondeterministic result (sound overapproximation)
|
|
803
|
+
# For float comparisons, as_float() returns payload; Z3 auto-coerces Int→Real
|
|
804
|
+
any_float = z3.Or(left.is_float(), right.is_float())
|
|
768
805
|
result_val = z3.If(
|
|
769
806
|
z3.Or(left.is_obj(), right.is_obj()),
|
|
770
807
|
z3.Int(f"cmp_lt_obj_{id(left)}_{id(right)}"),
|
|
771
|
-
z3.If(
|
|
808
|
+
z3.If(
|
|
809
|
+
any_float,
|
|
810
|
+
z3.If(left.as_float() < right.as_float(), z3.IntVal(1), z3.IntVal(0)),
|
|
811
|
+
z3.If(left.as_int() < right.as_int(), z3.IntVal(1), z3.IntVal(0))
|
|
812
|
+
)
|
|
772
813
|
)
|
|
773
814
|
return SymbolicValue(ValueTag.BOOL, result_val), type_ok
|
|
774
815
|
|
|
@@ -776,16 +817,25 @@ def compare_op_lt(left: SymbolicValue, right: SymbolicValue, solver: z3.Solver)
|
|
|
776
817
|
def compare_op_le(left: SymbolicValue, right: SymbolicValue, solver: z3.Solver) -> tuple[SymbolicValue, z3.ExprRef]:
|
|
777
818
|
"""Symbolic less-than-or-equal comparison."""
|
|
778
819
|
# Accept OBJ types (conservative overapproximation for soundness)
|
|
820
|
+
# Also accept float and mixed int/float comparisons
|
|
779
821
|
type_ok = z3.Or(
|
|
780
822
|
z3.And(left.is_int(), right.is_int()),
|
|
823
|
+
z3.And(left.is_float(), right.is_float()),
|
|
824
|
+
z3.And(left.is_int(), right.is_float()),
|
|
825
|
+
z3.And(left.is_float(), right.is_int()),
|
|
781
826
|
left.is_obj(),
|
|
782
827
|
right.is_obj()
|
|
783
828
|
)
|
|
784
829
|
# When OBJ involved, return nondeterministic result (sound overapproximation)
|
|
830
|
+
any_float = z3.Or(left.is_float(), right.is_float())
|
|
785
831
|
result_val = z3.If(
|
|
786
832
|
z3.Or(left.is_obj(), right.is_obj()),
|
|
787
833
|
z3.Int(f"cmp_le_obj_{id(left)}_{id(right)}"),
|
|
788
|
-
z3.If(
|
|
834
|
+
z3.If(
|
|
835
|
+
any_float,
|
|
836
|
+
z3.If(left.as_float() <= right.as_float(), z3.IntVal(1), z3.IntVal(0)),
|
|
837
|
+
z3.If(left.as_int() <= right.as_int(), z3.IntVal(1), z3.IntVal(0))
|
|
838
|
+
)
|
|
789
839
|
)
|
|
790
840
|
return SymbolicValue(ValueTag.BOOL, result_val), type_ok
|
|
791
841
|
|
|
@@ -899,16 +949,25 @@ def compare_op_ne(left: SymbolicValue, right: SymbolicValue, solver: z3.Solver)
|
|
|
899
949
|
def compare_op_gt(left: SymbolicValue, right: SymbolicValue, solver: z3.Solver) -> tuple[SymbolicValue, z3.ExprRef]:
|
|
900
950
|
"""Symbolic greater-than comparison."""
|
|
901
951
|
# Accept OBJ types (conservative overapproximation for soundness)
|
|
952
|
+
# Also accept float and mixed int/float comparisons
|
|
902
953
|
type_ok = z3.Or(
|
|
903
954
|
z3.And(left.is_int(), right.is_int()),
|
|
955
|
+
z3.And(left.is_float(), right.is_float()),
|
|
956
|
+
z3.And(left.is_int(), right.is_float()),
|
|
957
|
+
z3.And(left.is_float(), right.is_int()),
|
|
904
958
|
left.is_obj(),
|
|
905
959
|
right.is_obj()
|
|
906
960
|
)
|
|
907
961
|
# When OBJ involved, return nondeterministic result (sound overapproximation)
|
|
962
|
+
any_float = z3.Or(left.is_float(), right.is_float())
|
|
908
963
|
result_val = z3.If(
|
|
909
964
|
z3.Or(left.is_obj(), right.is_obj()),
|
|
910
965
|
z3.Int(f"cmp_gt_obj_{id(left)}_{id(right)}"),
|
|
911
|
-
z3.If(
|
|
966
|
+
z3.If(
|
|
967
|
+
any_float,
|
|
968
|
+
z3.If(left.as_float() > right.as_float(), z3.IntVal(1), z3.IntVal(0)),
|
|
969
|
+
z3.If(left.as_int() > right.as_int(), z3.IntVal(1), z3.IntVal(0))
|
|
970
|
+
)
|
|
912
971
|
)
|
|
913
972
|
return SymbolicValue(ValueTag.BOOL, result_val), type_ok
|
|
914
973
|
|
|
@@ -916,16 +975,25 @@ def compare_op_gt(left: SymbolicValue, right: SymbolicValue, solver: z3.Solver)
|
|
|
916
975
|
def compare_op_ge(left: SymbolicValue, right: SymbolicValue, solver: z3.Solver) -> tuple[SymbolicValue, z3.ExprRef]:
|
|
917
976
|
"""Symbolic greater-than-or-equal comparison."""
|
|
918
977
|
# Accept OBJ types (conservative overapproximation for soundness)
|
|
978
|
+
# Also accept float and mixed int/float comparisons
|
|
919
979
|
type_ok = z3.Or(
|
|
920
980
|
z3.And(left.is_int(), right.is_int()),
|
|
981
|
+
z3.And(left.is_float(), right.is_float()),
|
|
982
|
+
z3.And(left.is_int(), right.is_float()),
|
|
983
|
+
z3.And(left.is_float(), right.is_int()),
|
|
921
984
|
left.is_obj(),
|
|
922
985
|
right.is_obj()
|
|
923
986
|
)
|
|
924
987
|
# When OBJ involved, return nondeterministic result (sound overapproximation)
|
|
988
|
+
any_float = z3.Or(left.is_float(), right.is_float())
|
|
925
989
|
result_val = z3.If(
|
|
926
990
|
z3.Or(left.is_obj(), right.is_obj()),
|
|
927
991
|
z3.Int(f"cmp_ge_obj_{id(left)}_{id(right)}"),
|
|
928
|
-
z3.If(
|
|
992
|
+
z3.If(
|
|
993
|
+
any_float,
|
|
994
|
+
z3.If(left.as_float() >= right.as_float(), z3.IntVal(1), z3.IntVal(0)),
|
|
995
|
+
z3.If(left.as_int() >= right.as_int(), z3.IntVal(1), z3.IntVal(0))
|
|
996
|
+
)
|
|
929
997
|
)
|
|
930
998
|
return SymbolicValue(ValueTag.BOOL, result_val), type_ok
|
|
931
999
|
|
|
@@ -992,8 +1060,14 @@ def is_true(value: SymbolicValue, solver: z3.Solver) -> z3.ExprRef:
|
|
|
992
1060
|
value.payload == z3.IntVal(0)
|
|
993
1061
|
)
|
|
994
1062
|
|
|
995
|
-
#
|
|
996
|
-
|
|
1063
|
+
# Float 0.0 → False
|
|
1064
|
+
is_zero_float = z3.And(
|
|
1065
|
+
value.tag == z3.IntVal(ValueTag.FLOAT.value),
|
|
1066
|
+
value.payload == z3.RealVal(0)
|
|
1067
|
+
)
|
|
1068
|
+
|
|
1069
|
+
# Value is false if it's None, False, 0, or 0.0
|
|
1070
|
+
is_false = z3.Or(is_none, is_false_bool, is_zero_int, is_zero_float)
|
|
997
1071
|
|
|
998
1072
|
# Value is true if it's not false
|
|
999
1073
|
return z3.Not(is_false)
|
|
@@ -1112,9 +1186,11 @@ def binary_op_subscript(container: SymbolicValue, index: SymbolicValue, heap, so
|
|
|
1112
1186
|
solver.pop()
|
|
1113
1187
|
return result, type_ok, bounds_violated, none_misuse
|
|
1114
1188
|
|
|
1115
|
-
# Symbolic dict or symbolic key:
|
|
1189
|
+
# Symbolic dict or symbolic key: create symbolic bounds check
|
|
1190
|
+
# KeyError is possible but not certain (e.g., if key is validated)
|
|
1116
1191
|
result = SymbolicValue.fresh_int("dict_subscript_symbolic", solver)
|
|
1117
|
-
bounds_violated = z3.
|
|
1192
|
+
bounds_violated = z3.Bool(f"dict_key_missing_{id(container)}_{id(index)}")
|
|
1193
|
+
# Add constraint: allow both paths (key exists / key missing)
|
|
1118
1194
|
solver.pop()
|
|
1119
1195
|
return result, type_ok, bounds_violated, none_misuse
|
|
1120
1196
|
solver.pop()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: a3-python
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.21
|
|
4
4
|
Summary: Catch real Python bugs before production — 99%+ accuracy, Z3 symbolic execution, LLM-powered false-positive filtering, zero-config GitHub CI
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "a3-python"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.21"
|
|
8
8
|
description = "Catch real Python bugs before production — 99%+ accuracy, Z3 symbolic execution, LLM-powered false-positive filtering, zero-config GitHub CI"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|