time-assurance-code-scanner 0.2.0rc2__py3-none-any.whl
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.
- config_detector/__init__.py +11 -0
- config_detector/analyzers/__init__.py +9 -0
- config_detector/analyzers/architecture.py +188 -0
- config_detector/analyzers/time_t_config.py +183 -0
- config_detector/build_parsers/__init__.py +10 -0
- config_detector/build_parsers/base.py +82 -0
- config_detector/build_parsers/cmake.py +113 -0
- config_detector/build_parsers/makefile.py +112 -0
- config_detector/cache.py +109 -0
- config_detector/cli.py +403 -0
- config_detector/detector.py +106 -0
- config_detector/hybrid_detector.py +384 -0
- config_detector/likelihoods.py +166 -0
- config_detector/llm_analyzer.py +368 -0
- config_detector/output/__init__.py +8 -0
- config_detector/output/likelihood.py +314 -0
- config_detector/validator.py +147 -0
- envui/__init__.py +4 -0
- envui/cli/__init__.py +4 -0
- envui/cli/derive_fields.py +147 -0
- envui/cli/env_wizard.py +381 -0
- envui/cli/io_utils.py +139 -0
- envui/cli/validate_env.py +156 -0
- envui/examples/env_config.sample.json +15 -0
- envui/schemas/environment.schema.json +151 -0
- report_renderer/__init__.py +8 -0
- report_renderer/__main__.py +8 -0
- report_renderer/cli.py +165 -0
- report_renderer/core.py +535 -0
- report_renderer/fixtures/malformed.json +20 -0
- report_renderer/fixtures/missing-fields.json +22 -0
- report_renderer/fixtures/typical.json +38 -0
- report_renderer/renderers.py +743 -0
- tacs/__init__.py +6 -0
- tacs/batch_render_reports.py +434 -0
- tacs/batch_repo_scan.py +390 -0
- tacs/batch_scan_repos.py +1556 -0
- tacs/cli.py +157 -0
- tacs/core/__init__.py +4 -0
- tacs/core/alias_scope.py +256 -0
- tacs/core/arithmetic_scanner.py +212 -0
- tacs/core/candidate_evidence.py +220 -0
- tacs/core/candidate_utils.py +238 -0
- tacs/core/config_validator.py +216 -0
- tacs/core/define_scanner.py +325 -0
- tacs/core/discovery_manager.py +389 -0
- tacs/core/env_capabilities.py +95 -0
- tacs/core/file_limits.py +68 -0
- tacs/core/function_analysis_response.py +438 -0
- tacs/core/function_analyzer.py +458 -0
- tacs/core/function_llm_client.py +1100 -0
- tacs/core/function_scanner.py +485 -0
- tacs/core/function_schemas.py +105 -0
- tacs/core/include_patterns.py +27 -0
- tacs/core/io_boundary_analyzer.py +706 -0
- tacs/core/io_format_parser.py +176 -0
- tacs/core/ir_adapter.py +465 -0
- tacs/core/ir_match.py +410 -0
- tacs/core/llm_client.py +2093 -0
- tacs/core/llm_errors.py +360 -0
- tacs/core/llm_logger.py +42 -0
- tacs/core/llm_prompt.py +45 -0
- tacs/core/llm_response.py +34 -0
- tacs/core/logging_config.py +128 -0
- tacs/core/metrics.py +92 -0
- tacs/core/migration_analyzer.py +373 -0
- tacs/core/model_assessment.py +368 -0
- tacs/core/path_utils.py +190 -0
- tacs/core/pipeline.py +3363 -0
- tacs/core/rule_catalog.py +206 -0
- tacs/core/run_ids.py +34 -0
- tacs/core/scan_session.py +761 -0
- tacs/core/schema.py +484 -0
- tacs/core/source_files.py +149 -0
- tacs/core/status_logger.py +97 -0
- tacs/core/struct_filter.py +108 -0
- tacs/core/typedef_scanner.py +246 -0
- tacs/fixtures/repo_lists/test_ilp32_repos.jsonl +7 -0
- tacs/fixtures/repo_lists/test_repos.jsonl +10 -0
- tacs/fixtures/repo_lists/test_time_t_size_repos.jsonl +7 -0
- tacs/llm/__init__.py +43 -0
- tacs/llm/env.py +166 -0
- tacs/llm/factory.py +29 -0
- tacs/llm/openai_models.py +187 -0
- tacs/python/y2038scan_fast_json_group.py +833 -0
- tacs/rules/retired_rule_ids.json +1 -0
- tacs/rules/y2038_sample_rules.json +6134 -0
- tacs/scan_command.py +288 -0
- time_assurance_code_scanner-0.2.0rc2.dist-info/METADATA +184 -0
- time_assurance_code_scanner-0.2.0rc2.dist-info/RECORD +94 -0
- time_assurance_code_scanner-0.2.0rc2.dist-info/WHEEL +5 -0
- time_assurance_code_scanner-0.2.0rc2.dist-info/entry_points.txt +2 -0
- time_assurance_code_scanner-0.2.0rc2.dist-info/licenses/LICENSE +203 -0
- time_assurance_code_scanner-0.2.0rc2.dist-info/top_level.txt +4 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Copyright (c) 2026 Y2038.com LLC
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Auto-Detection Configuration Utility
|
|
6
|
+
|
|
7
|
+
A separate Python utility that analyzes build systems and outputs likelihood scores
|
|
8
|
+
for all eight possible environment configurations.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
__version__ = "1.0.0"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Copyright (c) 2026 Y2038.com LLC
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""Analyzers for detecting architecture, time_t configuration, and C library."""
|
|
5
|
+
|
|
6
|
+
from .architecture import ArchitectureDetector
|
|
7
|
+
from .time_t_config import TimeTConfigDetector
|
|
8
|
+
|
|
9
|
+
__all__ = ['ArchitectureDetector', 'TimeTConfigDetector']
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Copyright (c) 2026 Y2038.com LLC
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""Architecture detection analyzer (ILP32 vs LP64)."""
|
|
5
|
+
|
|
6
|
+
import re
|
|
7
|
+
from typing import Dict, Tuple, Optional, List
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class ArchitectureEvidence:
|
|
13
|
+
"""Evidence for architecture detection."""
|
|
14
|
+
source: str # "makefile", "cmake", "toolchain", etc.
|
|
15
|
+
indicator: str # What was detected
|
|
16
|
+
architecture: str # "ILP32" or "LP64"
|
|
17
|
+
confidence: float # 0.0 to 1.0
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ArchitectureDetector:
|
|
21
|
+
"""Detects hardware architecture (ILP32 vs LP64) from build system information."""
|
|
22
|
+
|
|
23
|
+
def __init__(self):
|
|
24
|
+
"""Initialize the architecture detector."""
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
def detect(self, extracted_info: Dict) -> Tuple[Optional[str], float, List[ArchitectureEvidence]]:
|
|
28
|
+
"""
|
|
29
|
+
Detect architecture from extracted build system information.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
extracted_info: Dictionary from build parser with 'cflags', 'defines',
|
|
33
|
+
'variables', 'toolchain'
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Tuple of (architecture, confidence, evidence_list)
|
|
37
|
+
- architecture: "ILP32", "LP64", or None if uncertain
|
|
38
|
+
- confidence: 0.0 to 1.0
|
|
39
|
+
- evidence_list: List of ArchitectureEvidence objects
|
|
40
|
+
"""
|
|
41
|
+
evidence: List[ArchitectureEvidence] = []
|
|
42
|
+
|
|
43
|
+
cflags = extracted_info.get('cflags', [])
|
|
44
|
+
variables = extracted_info.get('variables', {})
|
|
45
|
+
toolchain = extracted_info.get('toolchain', '')
|
|
46
|
+
|
|
47
|
+
# Check explicit architecture flags (highest confidence)
|
|
48
|
+
for flag in cflags:
|
|
49
|
+
if flag == '-m32':
|
|
50
|
+
evidence.append(ArchitectureEvidence(
|
|
51
|
+
source='compiler_flag',
|
|
52
|
+
indicator='-m32',
|
|
53
|
+
architecture='ILP32',
|
|
54
|
+
confidence=0.95
|
|
55
|
+
))
|
|
56
|
+
elif flag == '-m64':
|
|
57
|
+
evidence.append(ArchitectureEvidence(
|
|
58
|
+
source='compiler_flag',
|
|
59
|
+
indicator='-m64',
|
|
60
|
+
architecture='LP64',
|
|
61
|
+
confidence=0.95
|
|
62
|
+
))
|
|
63
|
+
elif flag.startswith('-march='):
|
|
64
|
+
arch = self._parse_march(flag)
|
|
65
|
+
if arch:
|
|
66
|
+
evidence.append(ArchitectureEvidence(
|
|
67
|
+
source='compiler_flag',
|
|
68
|
+
indicator=flag,
|
|
69
|
+
architecture=arch[0],
|
|
70
|
+
confidence=arch[1]
|
|
71
|
+
))
|
|
72
|
+
|
|
73
|
+
# Check toolchain (medium-high confidence)
|
|
74
|
+
if toolchain:
|
|
75
|
+
arch = self._parse_toolchain(toolchain)
|
|
76
|
+
if arch:
|
|
77
|
+
evidence.append(ArchitectureEvidence(
|
|
78
|
+
source='toolchain',
|
|
79
|
+
indicator=toolchain,
|
|
80
|
+
architecture=arch[0],
|
|
81
|
+
confidence=arch[1]
|
|
82
|
+
))
|
|
83
|
+
|
|
84
|
+
# Check CMAKE_SYSTEM_PROCESSOR (medium confidence)
|
|
85
|
+
if 'CMAKE_SYSTEM_PROCESSOR' in variables:
|
|
86
|
+
processor = variables['CMAKE_SYSTEM_PROCESSOR'].lower()
|
|
87
|
+
arch = self._parse_processor(processor)
|
|
88
|
+
if arch:
|
|
89
|
+
evidence.append(ArchitectureEvidence(
|
|
90
|
+
source='cmake',
|
|
91
|
+
indicator=f'CMAKE_SYSTEM_PROCESSOR={processor}',
|
|
92
|
+
architecture=arch[0],
|
|
93
|
+
confidence=arch[1]
|
|
94
|
+
))
|
|
95
|
+
|
|
96
|
+
# Determine final architecture and confidence
|
|
97
|
+
if not evidence:
|
|
98
|
+
return None, 0.0, []
|
|
99
|
+
|
|
100
|
+
# Count evidence for each architecture
|
|
101
|
+
ilp32_evidence = [e for e in evidence if e.architecture == 'ILP32']
|
|
102
|
+
lp64_evidence = [e for e in evidence if e.architecture == 'LP64']
|
|
103
|
+
|
|
104
|
+
# If conflicting evidence, use highest confidence
|
|
105
|
+
if ilp32_evidence and lp64_evidence:
|
|
106
|
+
ilp32_max = max(e.confidence for e in ilp32_evidence)
|
|
107
|
+
lp64_max = max(e.confidence for e in lp64_evidence)
|
|
108
|
+
|
|
109
|
+
if ilp32_max > lp64_max:
|
|
110
|
+
return 'ILP32', ilp32_max * 0.9, evidence # Slightly reduce due to conflict
|
|
111
|
+
else:
|
|
112
|
+
return 'LP64', lp64_max * 0.9, evidence
|
|
113
|
+
elif ilp32_evidence:
|
|
114
|
+
max_conf = max(e.confidence for e in ilp32_evidence)
|
|
115
|
+
return 'ILP32', max_conf, evidence
|
|
116
|
+
elif lp64_evidence:
|
|
117
|
+
max_conf = max(e.confidence for e in lp64_evidence)
|
|
118
|
+
return 'LP64', max_conf, evidence
|
|
119
|
+
|
|
120
|
+
return None, 0.0, evidence
|
|
121
|
+
|
|
122
|
+
def _parse_march(self, flag: str) -> Optional[Tuple[str, float]]:
|
|
123
|
+
"""Parse -march= flag to determine architecture."""
|
|
124
|
+
match = re.search(r'-march=([\w-]+)', flag)
|
|
125
|
+
if not match:
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
arch_str = match.group(1).lower()
|
|
129
|
+
|
|
130
|
+
# ILP32 indicators
|
|
131
|
+
if any(x in arch_str for x in ['armv7', 'armv6', 'armv5', 'armv4']):
|
|
132
|
+
return ('ILP32', 0.85)
|
|
133
|
+
if arch_str in ['i386', 'i686']:
|
|
134
|
+
return ('ILP32', 0.85)
|
|
135
|
+
|
|
136
|
+
# LP64 indicators
|
|
137
|
+
if arch_str in ['x86_64', 'amd64']:
|
|
138
|
+
return ('LP64', 0.95)
|
|
139
|
+
if 'armv8' in arch_str or 'aarch64' in arch_str:
|
|
140
|
+
return ('LP64', 0.90)
|
|
141
|
+
if arch_str.startswith('mips64'):
|
|
142
|
+
return ('LP64', 0.85)
|
|
143
|
+
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
def _parse_toolchain(self, toolchain: str) -> Optional[Tuple[str, float]]:
|
|
147
|
+
"""Parse toolchain name to determine architecture."""
|
|
148
|
+
toolchain_lower = toolchain.lower()
|
|
149
|
+
|
|
150
|
+
# ILP32 toolchains
|
|
151
|
+
if 'arm-none-eabi' in toolchain_lower or 'arm-eabi' in toolchain_lower:
|
|
152
|
+
return ('ILP32', 0.85)
|
|
153
|
+
if toolchain_lower.startswith('arm-'):
|
|
154
|
+
return ('ILP32', 0.80)
|
|
155
|
+
if 'mips32' in toolchain_lower:
|
|
156
|
+
return ('ILP32', 0.80)
|
|
157
|
+
|
|
158
|
+
# LP64 toolchains
|
|
159
|
+
if 'aarch64' in toolchain_lower:
|
|
160
|
+
return ('LP64', 0.90)
|
|
161
|
+
if 'x86_64' in toolchain_lower or 'amd64' in toolchain_lower:
|
|
162
|
+
return ('LP64', 0.90)
|
|
163
|
+
if 'mips64' in toolchain_lower:
|
|
164
|
+
return ('LP64', 0.85)
|
|
165
|
+
|
|
166
|
+
return None
|
|
167
|
+
|
|
168
|
+
def _parse_processor(self, processor: str) -> Optional[Tuple[str, float]]:
|
|
169
|
+
"""Parse CMAKE_SYSTEM_PROCESSOR to determine architecture."""
|
|
170
|
+
processor_lower = processor.lower()
|
|
171
|
+
|
|
172
|
+
# ILP32 processors
|
|
173
|
+
if processor_lower in ['arm', 'armv7', 'armv6', 'armv5']:
|
|
174
|
+
return ('ILP32', 0.80)
|
|
175
|
+
if processor_lower in ['i386', 'i686']:
|
|
176
|
+
return ('ILP32', 0.80)
|
|
177
|
+
if 'mips32' in processor_lower:
|
|
178
|
+
return ('ILP32', 0.75)
|
|
179
|
+
|
|
180
|
+
# LP64 processors
|
|
181
|
+
if processor_lower in ['aarch64', 'arm64', 'armv8']:
|
|
182
|
+
return ('LP64', 0.85)
|
|
183
|
+
if processor_lower in ['x86_64', 'amd64']:
|
|
184
|
+
return ('LP64', 0.90)
|
|
185
|
+
if 'mips64' in processor_lower:
|
|
186
|
+
return ('LP64', 0.80)
|
|
187
|
+
|
|
188
|
+
return None
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Copyright (c) 2026 Y2038.com LLC
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""time_t configuration detection analyzer."""
|
|
5
|
+
|
|
6
|
+
import re
|
|
7
|
+
from typing import Dict, Tuple, Optional, List
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class TimeTEvidence:
|
|
13
|
+
"""Evidence for time_t configuration detection."""
|
|
14
|
+
source: str # "makefile", "cmake", etc.
|
|
15
|
+
indicator: str # What was detected
|
|
16
|
+
time_t_size: Optional[int] = None # 32 or 64, or None
|
|
17
|
+
time_t_signed: Optional[str] = None # "signed" or "unsigned" or None
|
|
18
|
+
confidence: float = 0.0 # 0.0 to 1.0
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TimeTConfigDetector:
|
|
22
|
+
"""Detects time_t size and signedness configuration from build system information."""
|
|
23
|
+
|
|
24
|
+
def __init__(self):
|
|
25
|
+
"""Initialize the time_t config detector."""
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
def detect(self, extracted_info: Dict, architecture: Optional[str] = None) -> Tuple[Optional[int], float, Optional[str], float, List[TimeTEvidence]]:
|
|
29
|
+
"""
|
|
30
|
+
Detect time_t size and signedness from extracted build system information.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
extracted_info: Dictionary from build parser
|
|
34
|
+
architecture: Detected architecture (ILP32 or LP64) for inference
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Tuple of (time_t_size, time_t_size_confidence, time_t_signed, time_t_signed_confidence, evidence_list)
|
|
38
|
+
- time_t_size: 32, 64, or None if uncertain
|
|
39
|
+
- time_t_size_confidence: 0.0 to 1.0
|
|
40
|
+
- time_t_signed: "signed" or "unsigned" or None if uncertain
|
|
41
|
+
- time_t_signed_confidence: 0.0 to 1.0
|
|
42
|
+
- evidence_list: List of TimeTEvidence objects
|
|
43
|
+
"""
|
|
44
|
+
evidence: List[TimeTEvidence] = []
|
|
45
|
+
|
|
46
|
+
defines = extracted_info.get('defines', [])
|
|
47
|
+
cflags = extracted_info.get('cflags', [])
|
|
48
|
+
|
|
49
|
+
# Check for explicit _TIME_BITS define (highest confidence)
|
|
50
|
+
time_bits_value = None
|
|
51
|
+
for define in defines:
|
|
52
|
+
if define.startswith('TIME_BITS='):
|
|
53
|
+
time_bits_value = define.split('=')[1]
|
|
54
|
+
elif define == 'TIME_BITS':
|
|
55
|
+
# Check cflags for -D_TIME_BITS=64
|
|
56
|
+
for flag in cflags:
|
|
57
|
+
match = re.search(r'-D_TIME_BITS=(\d+)', flag)
|
|
58
|
+
if match:
|
|
59
|
+
time_bits_value = match.group(1)
|
|
60
|
+
break
|
|
61
|
+
|
|
62
|
+
# Also check cflags directly
|
|
63
|
+
for flag in cflags:
|
|
64
|
+
match = re.search(r'-D_TIME_BITS=(\d+)', flag)
|
|
65
|
+
if match:
|
|
66
|
+
time_bits_value = match.group(1)
|
|
67
|
+
evidence.append(TimeTEvidence(
|
|
68
|
+
source='compiler_flag',
|
|
69
|
+
indicator=f'-D_TIME_BITS={time_bits_value}',
|
|
70
|
+
time_t_size=int(time_bits_value),
|
|
71
|
+
confidence=0.95
|
|
72
|
+
))
|
|
73
|
+
break
|
|
74
|
+
|
|
75
|
+
# Detect time_t signedness
|
|
76
|
+
time_t_signed = None
|
|
77
|
+
time_t_signed_confidence = 0.0
|
|
78
|
+
|
|
79
|
+
# Check for explicit unsigned time_t defines
|
|
80
|
+
for define in defines:
|
|
81
|
+
if 'TIME_T_UNSIGNED' in define.upper() or 'CONFIG_TIME_T_UNSIGNED' in define.upper():
|
|
82
|
+
time_t_signed = 'unsigned'
|
|
83
|
+
time_t_signed_confidence = 0.85
|
|
84
|
+
evidence.append(TimeTEvidence(
|
|
85
|
+
source='define',
|
|
86
|
+
indicator=define,
|
|
87
|
+
time_t_signed='unsigned',
|
|
88
|
+
confidence=0.85
|
|
89
|
+
))
|
|
90
|
+
break
|
|
91
|
+
|
|
92
|
+
# Check cflags for unsigned time_t
|
|
93
|
+
for flag in cflags:
|
|
94
|
+
if '-D_TIME_T_UNSIGNED' in flag or '-DCONFIG_TIME_T_UNSIGNED' in flag:
|
|
95
|
+
time_t_signed = 'unsigned'
|
|
96
|
+
time_t_signed_confidence = 0.85
|
|
97
|
+
evidence.append(TimeTEvidence(
|
|
98
|
+
source='compiler_flag',
|
|
99
|
+
indicator=flag,
|
|
100
|
+
time_t_signed='unsigned',
|
|
101
|
+
confidence=0.85
|
|
102
|
+
))
|
|
103
|
+
break
|
|
104
|
+
|
|
105
|
+
# Infer signedness from library (if we can detect it)
|
|
106
|
+
# Most standard libraries (glibc, musl) use signed by default
|
|
107
|
+
# Some embedded libraries (Zephyr, picolibc) may use unsigned
|
|
108
|
+
# For now, default to signed if not explicitly detected
|
|
109
|
+
if time_t_signed is None:
|
|
110
|
+
# Default to signed (most common), but with low confidence
|
|
111
|
+
time_t_signed = 'signed'
|
|
112
|
+
time_t_signed_confidence = 0.5 # Low confidence default
|
|
113
|
+
|
|
114
|
+
if time_bits_value:
|
|
115
|
+
# Add signedness to evidence if not already added
|
|
116
|
+
if time_t_signed and not any(e.time_t_signed == time_t_signed for e in evidence):
|
|
117
|
+
evidence.append(TimeTEvidence(
|
|
118
|
+
source='default',
|
|
119
|
+
indicator=f'Default {time_t_signed} (not explicitly detected)',
|
|
120
|
+
time_t_signed=time_t_signed,
|
|
121
|
+
confidence=time_t_signed_confidence
|
|
122
|
+
))
|
|
123
|
+
return int(time_bits_value), 0.95, time_t_signed, time_t_signed_confidence, evidence
|
|
124
|
+
|
|
125
|
+
# Check for _FILE_OFFSET_BITS=64 (hint for 64-bit time_t)
|
|
126
|
+
for flag in cflags:
|
|
127
|
+
if '-D_FILE_OFFSET_BITS=64' in flag:
|
|
128
|
+
evidence.append(TimeTEvidence(
|
|
129
|
+
source='compiler_flag',
|
|
130
|
+
indicator='-D_FILE_OFFSET_BITS=64',
|
|
131
|
+
time_t_size=64,
|
|
132
|
+
time_t_signed=time_t_signed,
|
|
133
|
+
confidence=0.60 # Lower confidence, just a hint
|
|
134
|
+
))
|
|
135
|
+
|
|
136
|
+
# Infer from architecture if no explicit flags
|
|
137
|
+
if architecture:
|
|
138
|
+
if architecture == 'LP64':
|
|
139
|
+
# LP64 typically uses 64-bit time_t by default
|
|
140
|
+
# Add signedness to evidence if not already added
|
|
141
|
+
if time_t_signed and not any(e.time_t_signed == time_t_signed for e in evidence):
|
|
142
|
+
evidence.append(TimeTEvidence(
|
|
143
|
+
source='default',
|
|
144
|
+
indicator=f'Default {time_t_signed} (not explicitly detected)',
|
|
145
|
+
time_t_signed=time_t_signed,
|
|
146
|
+
confidence=time_t_signed_confidence
|
|
147
|
+
))
|
|
148
|
+
evidence.append(TimeTEvidence(
|
|
149
|
+
source='architecture_inference',
|
|
150
|
+
indicator=f'LP64 architecture (default 64-bit time_t)',
|
|
151
|
+
time_t_size=64,
|
|
152
|
+
time_t_signed=time_t_signed,
|
|
153
|
+
confidence=0.70
|
|
154
|
+
))
|
|
155
|
+
return 64, 0.70, time_t_signed, time_t_signed_confidence, evidence
|
|
156
|
+
elif architecture == 'ILP32':
|
|
157
|
+
# ILP32 typically uses 32-bit time_t by default (unless explicitly set)
|
|
158
|
+
# Add signedness to evidence if not already added
|
|
159
|
+
if time_t_signed and not any(e.time_t_signed == time_t_signed for e in evidence):
|
|
160
|
+
evidence.append(TimeTEvidence(
|
|
161
|
+
source='default',
|
|
162
|
+
indicator=f'Default {time_t_signed} (not explicitly detected)',
|
|
163
|
+
time_t_signed=time_t_signed,
|
|
164
|
+
confidence=time_t_signed_confidence
|
|
165
|
+
))
|
|
166
|
+
evidence.append(TimeTEvidence(
|
|
167
|
+
source='architecture_inference',
|
|
168
|
+
indicator=f'ILP32 architecture (default 32-bit time_t)',
|
|
169
|
+
time_t_size=32,
|
|
170
|
+
time_t_signed=time_t_signed,
|
|
171
|
+
confidence=0.65
|
|
172
|
+
))
|
|
173
|
+
return 32, 0.65, time_t_signed, time_t_signed_confidence, evidence
|
|
174
|
+
|
|
175
|
+
# No evidence found - still return signedness defaults
|
|
176
|
+
if time_t_signed and not any(e.time_t_signed == time_t_signed for e in evidence):
|
|
177
|
+
evidence.append(TimeTEvidence(
|
|
178
|
+
source='default',
|
|
179
|
+
indicator=f'Default {time_t_signed} (not explicitly detected)',
|
|
180
|
+
time_t_signed=time_t_signed,
|
|
181
|
+
confidence=time_t_signed_confidence
|
|
182
|
+
))
|
|
183
|
+
return None, 0.0, time_t_signed, time_t_signed_confidence, evidence
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Copyright (c) 2026 Y2038.com LLC
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""Build system parsers for extracting compiler flags and configuration."""
|
|
5
|
+
|
|
6
|
+
from .base import BaseParser
|
|
7
|
+
from .makefile import MakefileParser
|
|
8
|
+
from .cmake import CMakeParser
|
|
9
|
+
|
|
10
|
+
__all__ = ['BaseParser', 'MakefileParser', 'CMakeParser']
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Copyright (c) 2026 Y2038.com LLC
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""Base parser interface for build system files."""
|
|
5
|
+
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import List, Dict, Set
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BaseParser(ABC):
|
|
12
|
+
"""Base class for build system parsers."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, root_path: Path):
|
|
15
|
+
"""
|
|
16
|
+
Initialize parser.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
root_path: Root directory of the project
|
|
20
|
+
"""
|
|
21
|
+
self.root_path = root_path
|
|
22
|
+
|
|
23
|
+
@abstractmethod
|
|
24
|
+
def find_build_files(self) -> List[Path]:
|
|
25
|
+
"""
|
|
26
|
+
Find relevant build system files.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
List of paths to build files
|
|
30
|
+
"""
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
@abstractmethod
|
|
34
|
+
def extract_flags(self, build_file: Path) -> Dict[str, any]:
|
|
35
|
+
"""
|
|
36
|
+
Extract compiler flags and configuration from a build file.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
build_file: Path to the build file
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Dictionary with extracted information:
|
|
43
|
+
- 'cflags': List of C compiler flags
|
|
44
|
+
- 'defines': List of -D defines
|
|
45
|
+
- 'variables': Dictionary of variable assignments
|
|
46
|
+
- 'toolchain': Toolchain information if found
|
|
47
|
+
"""
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
def extract_all(self) -> Dict[str, any]:
|
|
51
|
+
"""
|
|
52
|
+
Extract all information from all build files.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Combined dictionary with all extracted information
|
|
56
|
+
"""
|
|
57
|
+
build_files = self.find_build_files()
|
|
58
|
+
|
|
59
|
+
all_cflags: Set[str] = set()
|
|
60
|
+
all_defines: Set[str] = set()
|
|
61
|
+
all_variables: Dict[str, str] = {}
|
|
62
|
+
toolchain: str = ""
|
|
63
|
+
|
|
64
|
+
for build_file in build_files:
|
|
65
|
+
try:
|
|
66
|
+
extracted = self.extract_flags(build_file)
|
|
67
|
+
all_cflags.update(extracted.get('cflags', []))
|
|
68
|
+
all_defines.update(extracted.get('defines', []))
|
|
69
|
+
all_variables.update(extracted.get('variables', {}))
|
|
70
|
+
if extracted.get('toolchain') and not toolchain:
|
|
71
|
+
toolchain = extracted.get('toolchain')
|
|
72
|
+
except Exception as e:
|
|
73
|
+
# Continue if one file fails
|
|
74
|
+
continue
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
'cflags': list(all_cflags),
|
|
78
|
+
'defines': list(all_defines),
|
|
79
|
+
'variables': all_variables,
|
|
80
|
+
'toolchain': toolchain,
|
|
81
|
+
'build_files_found': len(build_files)
|
|
82
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Copyright (c) 2026 Y2038.com LLC
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""CMake parser for extracting compiler flags and configuration."""
|
|
5
|
+
|
|
6
|
+
import re
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Dict, List, Set
|
|
9
|
+
from .base import BaseParser
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CMakeParser(BaseParser):
|
|
13
|
+
"""Parser for CMake build systems."""
|
|
14
|
+
|
|
15
|
+
def find_build_files(self) -> List[Path]:
|
|
16
|
+
"""Find CMakeLists.txt files in the project."""
|
|
17
|
+
build_files = []
|
|
18
|
+
seen_files = set()
|
|
19
|
+
|
|
20
|
+
# Check root directory first
|
|
21
|
+
cmake_file = self.root_path / 'CMakeLists.txt'
|
|
22
|
+
if cmake_file.exists():
|
|
23
|
+
build_files.append(cmake_file)
|
|
24
|
+
seen_files.add(cmake_file)
|
|
25
|
+
|
|
26
|
+
# Search for CMakeLists.txt in subdirectories (limit depth)
|
|
27
|
+
for cmake_file in self.root_path.rglob('CMakeLists.txt'):
|
|
28
|
+
if cmake_file.is_file() and cmake_file not in seen_files:
|
|
29
|
+
# Limit to reasonable depth and common locations
|
|
30
|
+
rel_path = cmake_file.relative_to(self.root_path)
|
|
31
|
+
if len(rel_path.parts) <= 4: # Max 4 levels deep
|
|
32
|
+
build_files.append(cmake_file)
|
|
33
|
+
seen_files.add(cmake_file)
|
|
34
|
+
|
|
35
|
+
return build_files[:10] # Limit to first 10
|
|
36
|
+
|
|
37
|
+
def extract_flags(self, build_file: Path) -> Dict[str, any]:
|
|
38
|
+
"""
|
|
39
|
+
Extract compiler flags from a CMakeLists.txt file.
|
|
40
|
+
|
|
41
|
+
Looks for:
|
|
42
|
+
- add_definitions(), add_compile_definitions()
|
|
43
|
+
- set(CMAKE_C_FLAGS ...)
|
|
44
|
+
- set(CMAKE_SYSTEM_PROCESSOR ...)
|
|
45
|
+
- find_package() calls
|
|
46
|
+
"""
|
|
47
|
+
try:
|
|
48
|
+
content = build_file.read_text(encoding='utf-8', errors='ignore')
|
|
49
|
+
except Exception:
|
|
50
|
+
return {'cflags': [], 'defines': [], 'variables': {}, 'toolchain': ''}
|
|
51
|
+
|
|
52
|
+
cflags: Set[str] = set()
|
|
53
|
+
defines: Set[str] = set()
|
|
54
|
+
variables: Dict[str, str] = {}
|
|
55
|
+
toolchain = ""
|
|
56
|
+
|
|
57
|
+
# Pattern for set() commands: set(VAR value) or set(VAR "value")
|
|
58
|
+
set_pattern = re.compile(r'set\s*\(\s*(\w+)\s+(.+?)\s*\)', re.IGNORECASE | re.DOTALL)
|
|
59
|
+
|
|
60
|
+
# Pattern for add_definitions: add_definitions(-DFOO -DBAR=value)
|
|
61
|
+
def_pattern = re.compile(r'add_(?:compile_)?definitions\s*\((.+?)\)', re.IGNORECASE | re.DOTALL)
|
|
62
|
+
|
|
63
|
+
# Pattern for CMAKE_SYSTEM_PROCESSOR
|
|
64
|
+
processor_pattern = re.compile(r'CMAKE_SYSTEM_PROCESSOR', re.IGNORECASE)
|
|
65
|
+
|
|
66
|
+
# Extract set() variables
|
|
67
|
+
for match in set_pattern.finditer(content):
|
|
68
|
+
var_name = match.group(1).upper()
|
|
69
|
+
var_value = match.group(2).strip()
|
|
70
|
+
|
|
71
|
+
# Remove quotes
|
|
72
|
+
var_value = re.sub(r'^["\']|["\']$', '', var_value)
|
|
73
|
+
# Remove comments
|
|
74
|
+
var_value = re.sub(r'#.*$', '', var_value).strip()
|
|
75
|
+
|
|
76
|
+
variables[var_name] = var_value
|
|
77
|
+
|
|
78
|
+
# Extract flags from CMAKE_C_FLAGS, etc.
|
|
79
|
+
if 'CMAKE_C_FLAGS' in var_name or 'CMAKE_CXX_FLAGS' in var_name:
|
|
80
|
+
flags = var_value.split()
|
|
81
|
+
for flag in flags:
|
|
82
|
+
flag = flag.strip()
|
|
83
|
+
if flag:
|
|
84
|
+
cflags.add(flag)
|
|
85
|
+
|
|
86
|
+
# Extract CMAKE_SYSTEM_PROCESSOR
|
|
87
|
+
if var_name == 'CMAKE_SYSTEM_PROCESSOR':
|
|
88
|
+
variables['CMAKE_SYSTEM_PROCESSOR'] = var_value
|
|
89
|
+
|
|
90
|
+
# Extract toolchain from CMAKE_C_COMPILER
|
|
91
|
+
if var_name == 'CMAKE_C_COMPILER' and not toolchain:
|
|
92
|
+
toolchain_match = re.match(r'^([\w-]+)-gcc', var_value)
|
|
93
|
+
if toolchain_match:
|
|
94
|
+
toolchain = toolchain_match.group(1)
|
|
95
|
+
|
|
96
|
+
# Extract add_definitions
|
|
97
|
+
for match in def_pattern.finditer(content):
|
|
98
|
+
defs_str = match.group(1)
|
|
99
|
+
# Extract -D defines
|
|
100
|
+
define_matches = re.finditer(r'-D(\w+(?:=\w+)?)', defs_str)
|
|
101
|
+
for def_match in define_matches:
|
|
102
|
+
defines.add(def_match.group(1))
|
|
103
|
+
|
|
104
|
+
# Look for find_package(Zephyr) or similar
|
|
105
|
+
if re.search(r'find_package\s*\(\s*Zephyr', content, re.IGNORECASE):
|
|
106
|
+
variables['ZEPHYR'] = 'true'
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
'cflags': list(cflags),
|
|
110
|
+
'defines': list(defines),
|
|
111
|
+
'variables': variables,
|
|
112
|
+
'toolchain': toolchain
|
|
113
|
+
}
|