|
| 1 | +#![cfg(feature = "wasm-precheck")] |
| 2 | +use codeguard_runtime::{WasmGrammar, scan_wasm_outer_returns}; |
| 3 | + |
| 4 | +fn javascript() -> WasmGrammar { |
| 5 | + WasmGrammar::load( |
| 6 | + "javascript", |
| 7 | + include_bytes!("../../../grammars/javascript/parser.wasm"), |
| 8 | + "7978e62bcc851ab1d1f6dcd4678f9eda79df2b3b3490e75f81dd819d9bccccfa", |
| 9 | + 15, |
| 10 | + ) |
| 11 | + .unwrap() |
| 12 | +} |
| 13 | +fn scan( |
| 14 | + tree: &tree_sitter::Tree, |
| 15 | + source: &[u8], |
| 16 | + records: usize, |
| 17 | + visits: usize, |
| 18 | +) -> codeguard_runtime::WasmOuterReturnScan { |
| 19 | + scan_wasm_outer_returns( |
| 20 | + tree, |
| 21 | + source, |
| 22 | + ["program", "return_statement"], |
| 23 | + &[ |
| 24 | + "function_declaration", |
| 25 | + "function_expression", |
| 26 | + "generator_function_declaration", |
| 27 | + "generator_function", |
| 28 | + "arrow_function", |
| 29 | + "method_definition", |
| 30 | + ], |
| 31 | + records, |
| 32 | + visits, |
| 33 | + ) |
| 34 | + .unwrap() |
| 35 | +} |
| 36 | +#[test] |
| 37 | +fn outside_function_returns_include_control_flow_and_byte_positions() { |
| 38 | + let mut grammar = javascript(); |
| 39 | + for source in [ |
| 40 | + "return 1;", |
| 41 | + "if (ready) { return 1; }", |
| 42 | + "while (ready) { return 2; }", |
| 43 | + "try { return 1; } catch (e) { return 2; }", |
| 44 | + "const 名字=1;\r\nif (名字) { return 名字; }", |
| 45 | + ] { |
| 46 | + let tree = grammar.parse(source.as_bytes()).unwrap(); |
| 47 | + assert!(!tree.root_node().has_error(), "{source}"); |
| 48 | + let facts = scan(&tree, source.as_bytes(), 128, 200_000); |
| 49 | + assert!(!facts.returns.is_empty(), "{source}"); |
| 50 | + assert!(!facts.truncated); |
| 51 | + for fact in &facts.returns { |
| 52 | + assert!(source[fact.start_byte..fact.end_byte].starts_with("return")); |
| 53 | + assert_eq!(fact.syntax_kind, "return_statement"); |
| 54 | + let prefix = &source.as_bytes()[..fact.start_byte]; |
| 55 | + assert_eq!( |
| 56 | + fact.start_row, |
| 57 | + prefix.iter().filter(|b| **b == b'\n').count() |
| 58 | + ); |
| 59 | + assert_eq!( |
| 60 | + fact.start_column_byte, |
| 61 | + prefix.rsplit(|b| *b == b'\n').next().unwrap().len() |
| 62 | + ); |
| 63 | + } |
| 64 | + } |
| 65 | +} |
| 66 | +#[test] |
| 67 | +fn every_function_boundary_and_comment_text_is_skipped() { |
| 68 | + let mut grammar = javascript(); |
| 69 | + for source in [ |
| 70 | + "function f(){if(true){return 1;}}", |
| 71 | + "const f = function(){return 1;};", |
| 72 | + "function* f(){return 1;}", |
| 73 | + "const f = function*(){return 1;};", |
| 74 | + "const f = () => {return 1;};", |
| 75 | + "class C { f(){return 1;} get x(){return 2;} }", |
| 76 | + "const o = {f(){return 1;}};", |
| 77 | + "async function f(){return 1;}", |
| 78 | + "const f = async () => {return 1;};", |
| 79 | + "const x = 'return 1;'; // return 2;\n", |
| 80 | + ] { |
| 81 | + let tree = grammar.parse(source.as_bytes()).unwrap(); |
| 82 | + assert!(!tree.root_node().has_error(), "{source}"); |
| 83 | + let facts = scan(&tree, source.as_bytes(), 128, 200_000); |
| 84 | + assert!(facts.returns.is_empty(), "{source}"); |
| 85 | + assert!(!facts.truncated); |
| 86 | + } |
| 87 | + let source = b"function f(){return 1;} if(true){return 2;}"; |
| 88 | + let tree = grammar.parse(source).unwrap(); |
| 89 | + let facts = scan(&tree, source, 128, 200_000); |
| 90 | + assert_eq!(facts.returns.len(), 1); |
| 91 | + assert_eq!( |
| 92 | + &source[facts.returns[0].start_byte..facts.returns[0].end_byte], |
| 93 | + b"return 2;" |
| 94 | + ); |
| 95 | +} |
| 96 | +#[test] |
| 97 | +fn exhausted_scan_retains_facts_and_invalid_bounds_are_rejected() { |
| 98 | + let mut grammar = javascript(); |
| 99 | + let source = b"return 1; return 2;"; |
| 100 | + let tree = grammar.parse(source).unwrap(); |
| 101 | + let facts = scan(&tree, source, 1, 200_000); |
| 102 | + assert_eq!(facts.returns.len(), 1); |
| 103 | + assert!(facts.truncated); |
| 104 | + let facts = scan(&tree, source, 128, 1); |
| 105 | + assert!(facts.returns.is_empty()); |
| 106 | + assert!(facts.truncated); |
| 107 | + for (root, ret, scopes, records, visits, bytes) in [ |
| 108 | + ( |
| 109 | + "program", |
| 110 | + "return_statement", |
| 111 | + &["function_declaration"][..], |
| 112 | + 0, |
| 113 | + 10, |
| 114 | + &source[..], |
| 115 | + ), |
| 116 | + ( |
| 117 | + "program", |
| 118 | + "return_statement", |
| 119 | + &["function_declaration"][..], |
| 120 | + 1, |
| 121 | + 0, |
| 122 | + &source[..], |
| 123 | + ), |
| 124 | + ( |
| 125 | + "wrong", |
| 126 | + "return_statement", |
| 127 | + &["function_declaration"][..], |
| 128 | + 1, |
| 129 | + 10, |
| 130 | + &source[..], |
| 131 | + ), |
| 132 | + ( |
| 133 | + "program", |
| 134 | + "", |
| 135 | + &["function_declaration"][..], |
| 136 | + 1, |
| 137 | + 10, |
| 138 | + &source[..], |
| 139 | + ), |
| 140 | + ("program", "return_statement", &[][..], 1, 10, &source[..]), |
| 141 | + ( |
| 142 | + "program", |
| 143 | + "return_statement", |
| 144 | + &["function_declaration"][..], |
| 145 | + 1, |
| 146 | + 10, |
| 147 | + &b""[..], |
| 148 | + ), |
| 149 | + ] { |
| 150 | + assert!( |
| 151 | + scan_wasm_outer_returns(&tree, bytes, [root, ret], scopes, records, visits).is_err() |
| 152 | + ); |
| 153 | + } |
| 154 | +} |
| 155 | + |
| 156 | +#[test] |
| 157 | +#[ignore = "需显式已有Node24.18.0,验证module/CommonJS模式边界,不批准语言资格"] |
| 158 | +fn actual_node_modes_confirm_outer_return_facts_without_commonjs_findings() { |
| 159 | + use codeguard_runtime::{ProcessSpec, Termination, run_process}; |
| 160 | + use std::{ |
| 161 | + collections::BTreeMap, |
| 162 | + ffi::OsString, |
| 163 | + path::{Path, PathBuf}, |
| 164 | + sync::atomic::AtomicBool, |
| 165 | + time::{Duration, Instant}, |
| 166 | + }; |
| 167 | + let input = PathBuf::from(std::env::var_os("CODEGUARD_NODE_BIN").expect("显式指定Node")); |
| 168 | + assert!(input.is_absolute()); |
| 169 | + let executable = input.canonicalize().unwrap(); |
| 170 | + let before = std::fs::read(&executable).unwrap(); |
| 171 | + let deadline = Instant::now() + Duration::from_secs(30); |
| 172 | + let cancelled = AtomicBool::new(false); |
| 173 | + let invoke = |args: &[&str], stdin| { |
| 174 | + run_process( |
| 175 | + &ProcessSpec { |
| 176 | + executable: executable.clone(), |
| 177 | + args: args.iter().map(OsString::from).collect(), |
| 178 | + cwd: Path::new("/").to_path_buf(), |
| 179 | + env: BTreeMap::new(), |
| 180 | + stdin, |
| 181 | + deadline, |
| 182 | + output_limit_bytes: 65536, |
| 183 | + }, |
| 184 | + &cancelled, |
| 185 | + ) |
| 186 | + }; |
| 187 | + let version = invoke(&["--version"], None); |
| 188 | + assert_eq!(version.termination, Termination::Exited(0)); |
| 189 | + assert_eq!(version.stdout, b"v24.18.0\n"); |
| 190 | + assert!(version.stderr.is_empty()); |
| 191 | + let mut grammar = javascript(); |
| 192 | + for source in [ |
| 193 | + "return 1;", |
| 194 | + "if(true){return 1;}", |
| 195 | + "while(false){return 1;}", |
| 196 | + "function f(){return 1;}", |
| 197 | + "const f=function(){return 1;};", |
| 198 | + "function* f(){return 1;}", |
| 199 | + "const f=function*(){return 1;};", |
| 200 | + "const f=()=>{return 1;};", |
| 201 | + "class C {f(){return 1;}}", |
| 202 | + "const o={f(){return 1;}};", |
| 203 | + "const s='return 1;';", |
| 204 | + ] { |
| 205 | + let tree = grammar.parse(source.as_bytes()).unwrap(); |
| 206 | + assert!(!tree.root_node().has_error()); |
| 207 | + let facts = scan(&tree, source.as_bytes(), 128, 200_000); |
| 208 | + assert!(!facts.truncated); |
| 209 | + for (mode, module) in [ |
| 210 | + ("--input-type=module", true), |
| 211 | + ("--input-type=commonjs", false), |
| 212 | + ] { |
| 213 | + let outcome = invoke(&["--check", mode], Some(source.as_bytes().to_vec())); |
| 214 | + let invalid = module && !facts.returns.is_empty(); |
| 215 | + assert_eq!( |
| 216 | + outcome.termination, |
| 217 | + Termination::Exited(i32::from(invalid)), |
| 218 | + "{mode} {source}" |
| 219 | + ); |
| 220 | + assert!(outcome.stdout.is_empty()); |
| 221 | + if invalid { |
| 222 | + assert!(String::from_utf8_lossy(&outcome.stderr).contains("SyntaxError")); |
| 223 | + } else { |
| 224 | + assert!(outcome.stderr.is_empty()); |
| 225 | + } |
| 226 | + } |
| 227 | + } |
| 228 | + assert_eq!(input.canonicalize().unwrap(), executable); |
| 229 | + assert_eq!(std::fs::read(executable).unwrap(), before); |
| 230 | +} |
0 commit comments