avm_rs/cli/commands/
validate.rs1use crate::assembler::Assembler;
4use crate::cli::{ExecutionMode, GlobalOptions, ValidateCommand};
5use anyhow::{Context, Result};
6use std::fs;
7use std::path::Path;
8
9pub fn handle(cmd: ValidateCommand, global: &GlobalOptions) -> Result<()> {
11 if !global.quiet && global.verbose {
12 println!("š Validating TEAL programs...");
13 println!("Files: {:?}", cmd.files);
14 }
15
16 let mut total_files = 0;
17 let mut valid_files = 0;
18 let mut warnings = 0;
19 let mut errors = 0;
20
21 for file_path in &cmd.files {
22 total_files += 1;
23
24 if !global.quiet && global.verbose {
25 println!("\nValidating: {file_path:?}");
26 }
27
28 match validate_file(file_path, &cmd, global) {
29 Ok(file_result) => {
30 valid_files += 1;
31 warnings += file_result.warnings;
32
33 if !global.quiet {
34 if file_result.warnings > 0 {
35 println!(
36 "ā ļø {:?}: Valid with {} warnings",
37 file_path, file_result.warnings
38 );
39 } else {
40 println!("ā
{file_path:?}: Valid");
41 }
42 }
43 }
44 Err(e) => {
45 errors += 1;
46 if !global.quiet {
47 println!("ā {file_path:?}: {e}");
48 }
49 }
50 }
51 }
52
53 if !global.quiet {
55 match global.format {
56 crate::cli::OutputFormat::Text => {
57 println!("\nš Validation Summary:");
58 println!(" Total files: {total_files}");
59 println!(" Valid files: {valid_files}");
60 println!(" Failed files: {errors}");
61 println!(" Total warnings: {warnings}");
62
63 if errors > 0 {
64 println!("ā Validation failed for {errors} files");
65 } else if warnings > 0 && cmd.strict {
66 println!("ā ļø Validation passed but {warnings} warnings in strict mode");
67 } else {
68 println!("ā
All files validated successfully");
69 }
70 }
71 crate::cli::OutputFormat::Json => {
72 let summary = serde_json::json!({
73 "total_files": total_files,
74 "valid_files": valid_files,
75 "failed_files": errors,
76 "total_warnings": warnings,
77 "success": errors == 0 && (!cmd.strict || warnings == 0)
78 });
79 println!("{}", serde_json::to_string_pretty(&summary)?);
80 }
81 }
82 }
83
84 if errors > 0 || (cmd.strict && warnings > 0) {
86 std::process::exit(1);
87 }
88
89 Ok(())
90}
91
92struct FileValidationResult {
94 warnings: usize,
95}
96
97fn validate_file(
99 file_path: &Path,
100 cmd: &ValidateCommand,
101 global: &GlobalOptions,
102) -> Result<FileValidationResult> {
103 let source = fs::read_to_string(file_path)
105 .with_context(|| format!("Failed to read file: {file_path:?}"))?;
106
107 let mut assembler = Assembler::new();
109 let _bytecode = assembler
110 .assemble(&source)
111 .map_err(|e| anyhow::anyhow!("Assembly failed: {}", e))?;
112
113 let mut warnings = 0;
115
116 if let Some(target_version) = cmd.version {
118 warnings += check_version_compatibility(&source, target_version)?;
119 }
120
121 if let Some(target_mode) = &cmd.mode {
123 warnings += check_mode_compatibility(&source, target_mode)?;
124 }
125
126 if cmd.detailed {
128 warnings += perform_detailed_analysis(&source, global)?;
129 }
130
131 Ok(FileValidationResult { warnings })
132}
133
134fn check_version_compatibility(source: &str, target_version: u8) -> Result<usize> {
136 let mut warnings = 0;
137
138 let declared_version = extract_pragma_version(source)?;
140
141 if let Some(declared) = declared_version {
142 if declared > target_version {
143 warnings += 1;
144 eprintln!("Warning: File declares version {declared} but target is {target_version}");
145 }
146 } else {
147 warnings += 1;
148 eprintln!("Warning: No version pragma found, assuming latest version");
149 }
150
151 Ok(warnings)
152}
153
154fn check_mode_compatibility(source: &str, target_mode: &ExecutionMode) -> Result<usize> {
156 let mut warnings = 0;
157
158 match target_mode {
160 ExecutionMode::Signature => {
161 if source.contains("app_global_get") || source.contains("app_local_get") {
162 warnings += 1;
163 eprintln!("Warning: Application opcodes found in signature mode validation");
164 }
165 }
166 ExecutionMode::Application => {
167 }
169 }
170
171 Ok(warnings)
172}
173
174fn perform_detailed_analysis(source: &str, global: &GlobalOptions) -> Result<usize> {
176 let mut warnings = 0;
177
178 let lines: Vec<&str> = source.lines().collect();
180
181 for (line_num, line) in lines.iter().enumerate() {
182 let line = line.trim();
183
184 if line.contains("int 0") && line.contains("==") {
186 warnings += 1;
187 if !global.quiet {
188 eprintln!(
189 "Warning: Line {}: Consider using '!' instead of '== 0'",
190 line_num + 1
191 );
192 }
193 }
194
195 if line.contains("b ") && !line.contains("bnz") && !line.contains("bz") && !global.quiet {
196 eprintln!("Info: Line {}: Unconditional branch found", line_num + 1);
197 }
198 }
199
200 if !source.contains("return") {
202 warnings += 1;
203 if !global.quiet {
204 eprintln!("Warning: No 'return' statement found");
205 }
206 }
207
208 Ok(warnings)
209}
210
211fn extract_pragma_version(source: &str) -> Result<Option<u8>> {
213 for line in source.lines() {
214 let line = line.trim();
215 if line.starts_with("#pragma version") {
216 let parts: Vec<&str> = line.split_whitespace().collect();
217 if parts.len() >= 3 {
218 return Ok(Some(parts[2].parse()?));
219 }
220 }
221 }
222 Ok(None)
223}