• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tamada / oinkie / 28728335077

05 Jul 2026 03:33AM UTC coverage: 73.696% (+1.2%) from 72.462%
28728335077

Pull #16

github

tamada
fix: escape CSV fields consistently and parse result files with the csv crate

escape_csv_string was applied only to matrix row/column names, while
Metadata::csv_info, Program::csv_info, Birthmark::csv_info and
CompareResult::to_csv wrote file names and paths verbatim. A path
containing a comma (or quote/newline) corrupted the result CSVs, and
the naive split(',') parsers in CompareResult::parse and
read_result_file then read back wrong paths, which silently poisons
--skip reruns and reaggregation.

- Escape every user-controlled field (names, paths) in the CSV writers
- Parse results.csv lines and per-pair CSV headers with the csv crate,
  which understands the quoting produced by the writers
- Export escape_csv_string through the prelude so the CLI crate shares
  the same escaping
- Add roundtrip tests with commas and quotes in paths

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pull Request #16: fix(security): CSV 出力のエスケープ漏れとパース不整合を修正

143 of 178 new or added lines in 7 files covered. (80.34%)

1 existing line in 1 file now uncovered.

1611 of 2186 relevant lines covered (73.7%)

12.22 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

56.47
/cli/cli.rs
1
use std::path::{Path, PathBuf};
2

3
pub use crate::info::BType;
4
use clap::ValueEnum;
5
use oinkie::prelude::*;
6

7
#[derive(Debug, clap::Parser)]
8
#[command(version, about)]
9
pub struct OinkieOpts {
10
    #[clap(subcommand)]
11
    pub command: OinkieCommand,
12

13
    #[clap(short, long, value_enum, default_value_t = LogLevel::Warn, value_name = "LEVEL", ignore_case = true, help = "Log level for the application")]
14
    pub level: LogLevel,
15
}
16

17
impl OinkieOpts {
18
    pub fn init(&self) -> Result<()> {
9✔
19
        let filter = match self.level {
9✔
NEW
20
            LogLevel::Debug => log::LevelFilter::Debug,
×
NEW
21
            LogLevel::Info => log::LevelFilter::Info,
×
22
            LogLevel::Warn => log::LevelFilter::Warn,
9✔
NEW
23
            LogLevel::Error => log::LevelFilter::Error,
×
NEW
24
            LogLevel::Trace => log::LevelFilter::Trace,
×
NEW
25
            LogLevel::Off => log::LevelFilter::Off,
×
26
        };
27
        env_logger::Builder::new().filter_level(filter).init();
9✔
28
        Ok(())
9✔
29
    }
9✔
30
}
31

32
#[derive(Debug, clap::Parser, ValueEnum, Clone)]
33
pub enum LogLevel {
34
    Error,
35
    Warn,
36
    Info,
37
    Debug,
38
    Trace,
39
    Off,
40
}
41

42
#[derive(Debug, clap::Parser)]
43
pub enum OinkieCommand {
44
    #[command(name="info", about = "Display information about the application")]
45
    Info,
46

47
    #[command(name="lift", about = "Lift binary files to P-code JSON files using a specified lifter")]
48
    Lift(LiftOpts),
49

50
    #[command(name="extract", about = "Extract birthmarks from a lifted binary file (JSON format)")]
51
    Extract(ExtractOpts),
52

53
    #[command(name="compare", about = "Compare birthmarks and output the similarity score")]
54
    Compare(CompareOpts),
55
    // #[command(name="execute", about = "Execute a command on the JSON files")]
56
    // Execute(ExecuteOpts),
57
    #[command(name="reaggregate", about = "Reaggregate the element-wise similarity scores and recalculate the birthmark-wise similarity score")]
58
    Reaggregate(ReaggregateOpts),
59

60
    #[command(name="run", about = "Extract birthmarks and compare them in one command")]
61
    Run(RunOpts),
62

63
    #[cfg(debug_assertions)]
64
    #[command(name="gencomp", about = "Generate completions")]
65
    GenComp,
66
}
67

68
#[derive(Debug, clap::Parser)]
69
pub struct LiftOpts {
70
    #[clap(short, long, default_value = "pcodes", value_name = "DIRECTORY", help = "Specify the directory for putting the resultant JSON files for the lifted P-code (default: './pcodes' directory)")]
71
    dest: PathBuf,
72

73
    #[clap(short = 'l', long, value_enum, default_value_t = LifterType::Ghidra, help = "Specify the lifter type")]
74
    lifter_type: LifterType,
75

76
    #[clap(short = 'H', long, value_name = "HOME", help = "Specify the path to the home directory of the lifter (e.g., GHIDRA_HOME for Ghidra). If not specified, the environment variable (e.g., GHIDRA_HOME) or default paths are searched.")]
77
    home: Option<PathBuf>,
78

79
    #[clap(short = 'i', long = "intermediate", value_name = "DIRECTORY", help = "Directory to keep intermediate files like Ghidra project directories. If not specified, a temporary directory is used and deleted.")]
80
    intermediate_dir: Option<PathBuf>,
81

82
    #[clap(long, value_name = "SCRIPT", help = "Path to a custom lifting script. Interpretation depends on the lifter type. For Ghidra, it's the path to a Java script.")]
83
    script: Option<PathBuf>,
84

85
    #[clap(short = 'S', long, default_value_t = false, help = "Skip if the resultant JSON file already exists")]
86
    skip: bool,
87

88
    #[clap(index = 1, value_name = "FILES", help = "Path to the binary or intermediate files to lift")]
89
    files: Vec<PathBuf>,
90
}
91

92
impl LiftOpts {
93
    pub fn dest(&self) -> &Path {
2✔
94
        &self.dest
2✔
95
    }
2✔
96

97
    pub fn lifter_type(&self) -> LifterType {
1✔
98
        self.lifter_type
1✔
99
    }
1✔
100

101
    pub fn home(&self) -> Option<&Path> {
2✔
102
        self.home.as_deref()
2✔
103
    }
2✔
104

105
    pub fn intermediate_dir(&self) -> Option<&Path> {
1✔
106
        self.intermediate_dir.as_deref()
1✔
107
    }
1✔
108

109
    pub fn script(&self) -> Option<&Path> {
1✔
110
        self.script.as_deref()
1✔
111
    }
1✔
112

113
    pub fn is_skip(&self) -> bool {
1✔
114
        self.skip
1✔
115
    }
1✔
116

117
    pub fn iter(&self) -> impl Iterator<Item = &PathBuf> {
2✔
118
        self.files.iter()
2✔
119
    }
2✔
120

121
    pub fn len(&self) -> usize {
1✔
122
        self.files.len()
1✔
123
    }
1✔
124
}
125

126
#[derive(Debug, clap::Parser, ValueEnum, Clone)]
127
#[clap(rename_all = "kebab-case")]
128
pub enum BinaryType {
129
    Ghidra,
130
    Llvm,
131
    BinaryNinja,
132
}
133

134
#[derive(Debug, clap::Parser)]
135
pub struct ExtractOpts {
136
    #[clap(short, long, default_value = "birthmarks", value_name = "DIRECTORY", help = "Specify the directory for putting the resultant JSON files for the extracted birthmarks (default: './birthmarks' directory)")]
137
    dest: PathBuf,
138

139
    #[clap(short, long, value_enum, value_name = "BIRTHMARK_TYPE", default_value_t = BType::OpSeq, hide_possible_values = true, ignore_case = true, help = "Type of birthmark to extract.
140
fc (Function Calls) and op (Opcode) with set, seq, and freq variants are supported.
141
For example, 'op-seq' extracts the sequence of operations as a birthmark,
142
while 'fc-freq' extracts the frequency of function calls.
143
The full birthmark types cann be found by running 'oinkie info'.")]
144
    birthmark_type: BType,
145

146
    #[clap(short = 'S', long, default_value_t = false, help = "Skip the resultant birthmark file is already exists")]
147
    skip: bool,
148

149
    #[clap(short = 'B', long, value_enum, value_name = "BINARY_TYPE", default_value_t = BinaryType::Ghidra, ignore_case = true, help = "Type of binary. Current version only supports Ghidra JSON format")]
150
    binary_type: BinaryType,
151

152
    #[clap(index = 1, value_name = "JSON_FILES", help = "Path to the JSON files to extract birthmarks from")]
153
    files: Vec<PathBuf>,
154
}
155

156
impl ExtractOpts {
157
    pub fn dest(&self) -> &Path {
3✔
158
        &self.dest
3✔
159
    }
3✔
160

161
    pub fn extractor(&self) -> Extractor {
3✔
162
        Extractor::new(self.birthmark_type.clone().into())
3✔
163
    }
3✔
164

165
    pub fn len(&self) -> usize {
3✔
166
        self.files.len()
3✔
167
    }
3✔
168

169
    pub fn is_empty(&self) -> bool {
3✔
170
        self.files.is_empty()
3✔
171
    }
3✔
172

173
    pub fn iter(&self) -> impl Iterator<Item = &PathBuf> {
3✔
174
        self.files.iter()
3✔
175
    }
3✔
176

177
    pub fn is_skip(&self) -> bool {
6✔
178
        self.skip
6✔
179
    }
6✔
180
}
181

182
#[derive(Debug, clap::Parser)]
183
pub struct ReaggregateOpts {
184
    #[clap(
185
        short = 'A', long, default_value = "hungarian", value_name = "METHOD", ignore_case = true, 
186
        help = "Specify the aggregator for combining element-wise similarity scores into a birthmark-wise similarity score.
187
Available: 
188
- hungarian  Use the Hungarian algorithm to find the optimal matching between elements of two birthmarks,
189
             maximizing the total similarity score.
190
- topn:N     For each element in the first birthmark, consider only the top N most similar elements in the
191
             second birthmark when calculating the overall similarity score. This can reduce noise from less
192
             relevant matches and focus on the most significant similarities."
193
    )]
194
    aggregator: Aggregator,
195

196
    #[clap(short, long, value_name = "RESULT.CSV", help = "Specify the result CSV file of the comparing results to reaggregate.
197
The file contains the birthmark-wise similarity score list.", default_value = "reaggregate.csv")]
198
    dest_file: PathBuf,
199

200
    #[clap(index = 1, value_name = "SCORE_DIRECTORY", help = "Path to the directory containing the element-wise similarity scores")]
201
    score_directory: PathBuf,
202
}
203

204
impl ReaggregateOpts {
205
    pub fn aggregator(&self) -> &Aggregator {
1✔
206
        &self.aggregator
1✔
207
    }
1✔
208

209
    pub fn score_directory(&self) -> &Path {
1✔
210
        &self.score_directory
1✔
211
    }
1✔
212

213
    pub fn dest_file(&self) -> &PathBuf {
1✔
214
        &self.dest_file
1✔
215
    }
1✔
216
}
217

218
#[derive(Debug, clap::Parser)]
219
pub struct CompareOpts {
220
    #[clap(short, long, value_enum, default_value_t = Algorithm::Jaccard, value_name = "ALGORITHM", ignore_case = true, help = "Specify the similarity calculation algorithm.")]
221
    algorithm: Algorithm,    
222

223
    #[clap(
224
        short = 'A', long, default_value = "hungarian", value_name = "METHOD", ignore_case = true, 
225
        help = "Specify the aggregator for combining element-wise similarity scores into a birthmark-wise similarity score.
226
Available: 
227
- hungarian  Use the Hungarian algorithm to find the optimal matching between elements of two birthmarks,
228
             maximizing the total similarity score.
229
- topn:N     For each element in the first birthmark, consider only the top N most similar elements in the
230
             second birthmark when calculating the overall similarity score. This can reduce noise from less
231
             relevant matches and focus on the most significant similarities."
232
    )]
233
    aggregator: Aggregator,
234

235
    #[clap(short, long, value_enum, default_value_t = PairingStrategy::AllAndSelf, value_name = "STRATEGY", ignore_case = true, help = "Specify the pairing strategy for comparing files.")]
236
    strategy: PairingStrategy,
237

238
    #[clap(short, long, value_name = "DIRECTORY", help = "Specify the destination directory for the comparing results", default_value = "similarities")]
239
    dest: PathBuf,
240

241
    #[clap(short = 'S', long, default_value_t = false, help = "Skip if the similarity file already exists for the pair of birthmarks")]
242
    skip: bool,
243

244
    #[clap(index = 1, value_name = "JSON_FILES", help = "Path to the birthmark JSON files to compare")]
245
    files: Vec<PathBuf>,
246
}
247

248
impl CompareOpts {
249
    pub fn dest(&self) -> &Path {
2✔
250
        &self.dest
2✔
251
    }
2✔
252

253
    pub fn comparator(&self) -> Comparator {
2✔
254
        self.algorithm.comparator()
2✔
255
    }
2✔
256

257
    pub fn iter(&self) -> Box<dyn Iterator<Item = (&PathBuf, &PathBuf)> + Send + '_> {
2✔
258
        self.strategy.pairs(&self.files)
2✔
259
    }
2✔
260

261
    pub fn aggregator(&self) -> &Aggregator {
2✔
262
        log::info!("Using {:?} as the aggregator for combining element-wise similarity scores", self.aggregator);
2✔
263
        &self.aggregator
2✔
264
    }
2✔
265

266
    pub fn compare_count(&self) -> usize {
2✔
267
        self.strategy.compare_count(&self.files)
2✔
268
    }
2✔
269

270
    pub fn is_skip(&self) -> bool {
2✔
271
        self.skip
2✔
272
    }
2✔
273
}
274

275
#[derive(Debug, clap::Parser)]
276
pub struct RunOpts {
277
    #[clap(short, long, value_enum, default_value_t = Analysis::OpSetJaccard, ignore_case = true, help = "Similarity algorithm to use")]
278
    pub(crate) analysis: Analysis,
279

280
    #[clap(short, long, value_enum, default_value_t = PairingStrategy::AllAndSelf, ignore_case = true, help = "Pairing strategy for file comparisons")]
281
    pub strategy: PairingStrategy,
282

283
    #[clap(short, long, default_value = "similarities", help = "Destination path for the output CSV file (default: 'similarities' directory")]
284
    pub(crate) dest: PathBuf,
285

286
    #[clap(
287
        short = 'A', long, default_value = "hungarian", value_name = "METHOD", ignore_case = true,
288
        help = "Specify the aggregator for combining element-wise similarity scores into a birthmark-wise similarity score.
289
Available: 
290
- hungarian  Use the Hungarian algorithm to find the optimal matching between elements of two birthmarks,
291
             maximizing the total similarity score.
292
- topn:N     For each element in the first birthmark, consider only the top N most similar elements in the
293
             second birthmark when calculating the overall similarity score. This can reduce noise from less
294
             relevant matches and focus on the most significant similarities. available topn:N or topn:all (same as topn)."
295
    )]
296
    aggregator: Aggregator,
297

298
    #[clap(short = 'S', long, default_value_t = false, help = "Skip if the similarity file already exists for the pair of birthmarks")]
299
    pub(crate) skip: bool,
300

301
    #[clap(index = 1, help = "Path to the JSON files")]
302
    pub(crate) files: Vec<PathBuf>,
303
}
304

305
impl RunOpts {
306
    pub fn dest(&self) -> &Path {
1✔
307
        &self.dest
1✔
308
    }
1✔
309

310
    pub fn analysis_type(&self) -> AnalysisType {
1✔
311
        (&self.analysis).into()
1✔
312
    }
1✔
313

314
    pub fn iter(&self) -> Box<dyn Iterator<Item = (&PathBuf, &PathBuf)> + Send + '_> {
1✔
315
        self.strategy.pairs(&self.files)
1✔
316
    }
1✔
317

318
    pub fn compare_count(&self) -> usize {
1✔
319
        self.strategy.compare_count(&self.files)
1✔
320
    }
1✔
321

322
    pub fn is_skip(&self) -> bool {
×
323
        self.skip
×
324
    }
×
325

326
    pub fn aggregator(&self) -> &Aggregator {
1✔
327
        log::info!("Using {:?} as the aggregator for combining element-wise similarity scores", self.aggregator);
1✔
328
        &self.aggregator
1✔
329
    }
1✔
330
}
331

332
#[derive(ValueEnum, Clone, Debug)]
333
#[clap(rename_all = "kebab-case")]
334
pub(crate) enum Analysis {
335
    FcFreqCosine,
336
    FcSetDice,
337
    FcFreqEuclidean,
338
    FcSetJaccard,
339
    FcSeqLevenshtein,
340
    FcSeqLcs,
341
    FcSetSimpson,
342
    FcFreqWeightedjaccard,
343

344
    OpFreqCosine,
345
    OpSetDice,
346
    OpFreqEuclidean,
347
    OpSetJaccard,
348
    OpSeqLevenshtein,
349
    OpSeqLcs,
350
    OpSetSimpson,
351
    OpFreqWeightedjaccard,
352

353
    Op1gramSetDice,
354
    Op1gramSetJaccard,
355
    Op1gramSetSimpson,
356
    Op1gramSeqLevenshtein,
357
    Op1gramSeqLcs,
358
    Op1gramFreqCosine,
359
    Op1gramFreqEuclidean,
360
    Op1gramFreqWeightedjaccard,
361

362
    Op2gramSetDice,
363
    Op2gramSetJaccard,
364
    Op2gramSetSimpson,
365
    Op2gramSeqLevenshtein,
366
    Op2gramSeqLcs,
367
    Op2gramFreqCosine,
368
    Op2gramFreqEuclidean,
369
    Op2gramFreqWeightedjaccard,
370

371
    Op3gramSetDice,
372
    Op3gramSetJaccard,
373
    Op3gramSetSimpson,
374
    Op3gramSeqLevenshtein,
375
    Op3gramSeqLcs,
376
    Op3gramFreqCosine,
377
    Op3gramFreqEuclidean,
378
    Op3gramFreqWeightedjaccard,
379

380
    Op4gramSetDice,
381
    Op4gramSetJaccard,
382
    Op4gramSetSimpson,
383
    Op4gramSeqLevenshtein,
384
    Op4gramSeqLcs,
385
    Op4gramFreqCosine,
386
    Op4gramFreqEuclidean,
387
    Op4gramFreqWeightedjaccard,
388

389
    Op5gramSetDice,
390
    Op5gramSetJaccard,
391
    Op5gramSetSimpson,
392
    Op5gramSeqLevenshtein,
393
    Op5gramSeqLcs,
394
    Op5gramFreqCosine,
395
    Op5gramFreqEuclidean,
396
    Op5gramFreqWeightedjaccard,
397

398
    Op6gramSetDice,
399
    Op6gramSetJaccard,
400
    Op6gramSetSimpson,
401
    Op6gramSeqLevenshtein,
402
    Op6gramSeqLcs,
403
    Op6gramFreqCosine,
404
    Op6gramFreqEuclidean,
405
    Op6gramFreqWeightedjaccard,
406
}
407

408
impl From<Analysis> for AnalysisType {
409
    fn from(v: Analysis) -> Self {
×
410
        AnalysisType::from(&v)
×
411
    }
×
412
}
413

414
impl From<&Analysis> for AnalysisType {
415
    fn from(value: &Analysis) -> Self {
1✔
416
        match value {
1✔
417
            Analysis::FcFreqCosine => AnalysisType::new(BirthmarkType::FcFreq, Algorithm::Cosine),
×
418
            Analysis::FcSetDice => AnalysisType::new(BirthmarkType::FcSet, Algorithm::Dice),
×
419
            Analysis::FcFreqEuclidean => AnalysisType::new(BirthmarkType::FcFreq, Algorithm::Euclidean),
×
420
            Analysis::FcSetJaccard => AnalysisType::new(BirthmarkType::FcSet, Algorithm::Jaccard),
×
421
            Analysis::FcSeqLevenshtein => AnalysisType::new(BirthmarkType::FcSeq, Algorithm::Levenshtein),
×
422
            Analysis::FcSeqLcs => AnalysisType::new(BirthmarkType::FcSeq, Algorithm::Lcs),
×
423
            Analysis::FcSetSimpson => AnalysisType::new(BirthmarkType::FcSet, Algorithm::Simpson),
×
424
            Analysis::FcFreqWeightedjaccard => AnalysisType::new(BirthmarkType::FcFreq, Algorithm::WeightedJaccard),
×
425

426
            Analysis::OpFreqCosine => AnalysisType::new(BirthmarkType::OpFreq, Algorithm::Cosine),
×
427
            Analysis::OpSetDice => AnalysisType::new(BirthmarkType::OpSet, Algorithm::Dice),
×
428
            Analysis::OpFreqEuclidean => AnalysisType::new(BirthmarkType::OpFreq, Algorithm::Euclidean),
×
429
            Analysis::OpSetJaccard => AnalysisType::new(BirthmarkType::OpSet, Algorithm::Jaccard),
1✔
430
            Analysis::OpSeqLevenshtein => AnalysisType::new(BirthmarkType::OpSeq, Algorithm::Levenshtein),
×
431
            Analysis::OpSeqLcs => AnalysisType::new(BirthmarkType::OpSeq, Algorithm::Lcs),
×
432
            Analysis::OpSetSimpson => AnalysisType::new(BirthmarkType::OpSet, Algorithm::Simpson),
×
433
            Analysis::OpFreqWeightedjaccard => AnalysisType::new(BirthmarkType::OpFreq, Algorithm::WeightedJaccard),
×
434

435
            Analysis::Op1gramSetDice => AnalysisType::new(BirthmarkType::OpKgramSet(1), Algorithm::Dice),
×
436
            Analysis::Op1gramSetJaccard => AnalysisType::new(BirthmarkType::OpKgramSet(1), Algorithm::Jaccard),
×
437
            Analysis::Op1gramSetSimpson => AnalysisType::new(BirthmarkType::OpKgramSet(1), Algorithm::Simpson),
×
438
            Analysis::Op1gramSeqLevenshtein => AnalysisType::new(BirthmarkType::OpKgramSeq(1), Algorithm::Levenshtein),
×
439
            Analysis::Op1gramSeqLcs => AnalysisType::new(BirthmarkType::OpKgramSeq(1), Algorithm::Lcs),
×
440
            Analysis::Op1gramFreqCosine => AnalysisType::new(BirthmarkType::OpKgramFreq(1), Algorithm::Cosine),
×
441
            Analysis::Op1gramFreqEuclidean => AnalysisType::new(BirthmarkType::OpKgramFreq(1), Algorithm::Euclidean),
×
442
            Analysis::Op1gramFreqWeightedjaccard => AnalysisType::new(BirthmarkType::OpKgramFreq(1), Algorithm::WeightedJaccard),
×
443

444
            Analysis::Op2gramSetDice => AnalysisType::new(BirthmarkType::OpKgramSet(2), Algorithm::Dice),
×
445
            Analysis::Op2gramSetJaccard => AnalysisType::new(BirthmarkType::OpKgramSet(2), Algorithm::Jaccard),
×
446
            Analysis::Op2gramSetSimpson => AnalysisType::new(BirthmarkType::OpKgramSet(2), Algorithm::Simpson),
×
447
            Analysis::Op2gramSeqLevenshtein => AnalysisType::new(BirthmarkType::OpKgramSeq(2), Algorithm::Levenshtein),
×
448
            Analysis::Op2gramSeqLcs => AnalysisType::new(BirthmarkType::OpKgramSeq(2), Algorithm::Lcs),
×
449
            Analysis::Op2gramFreqCosine => AnalysisType::new(BirthmarkType::OpKgramFreq(2), Algorithm::Cosine),
×
450
            Analysis::Op2gramFreqEuclidean => AnalysisType::new(BirthmarkType::OpKgramFreq(2), Algorithm::Euclidean),
×
451
            Analysis::Op2gramFreqWeightedjaccard => AnalysisType::new(BirthmarkType::OpKgramFreq(2), Algorithm::WeightedJaccard),
×
452

453
            Analysis::Op3gramSetDice => AnalysisType::new(BirthmarkType::OpKgramSet(3), Algorithm::Dice),
×
454
            Analysis::Op3gramSetJaccard => AnalysisType::new(BirthmarkType::OpKgramSet(3), Algorithm::Jaccard),
×
455
            Analysis::Op3gramSetSimpson => AnalysisType::new(BirthmarkType::OpKgramSet(3), Algorithm::Simpson),
×
456
            Analysis::Op3gramSeqLevenshtein => AnalysisType::new(BirthmarkType::OpKgramSeq(3), Algorithm::Levenshtein),
×
457
            Analysis::Op3gramSeqLcs => AnalysisType::new(BirthmarkType::OpKgramSeq(3), Algorithm::Lcs),
×
458
            Analysis::Op3gramFreqCosine => AnalysisType::new(BirthmarkType::OpKgramFreq(3), Algorithm::Cosine),
×
459
            Analysis::Op3gramFreqEuclidean => AnalysisType::new(BirthmarkType::OpKgramFreq(3), Algorithm::Euclidean),
×
460
            Analysis::Op3gramFreqWeightedjaccard => AnalysisType::new(BirthmarkType::OpKgramFreq(3), Algorithm::WeightedJaccard),
×
461

462
            Analysis::Op4gramSetDice => AnalysisType::new(BirthmarkType::OpKgramSet(4), Algorithm::Dice),
×
463
            Analysis::Op4gramSetJaccard => AnalysisType::new(BirthmarkType::OpKgramSet(4), Algorithm::Jaccard),
×
464
            Analysis::Op4gramSetSimpson => AnalysisType::new(BirthmarkType::OpKgramSet(4), Algorithm::Simpson),
×
465
            Analysis::Op4gramSeqLevenshtein => AnalysisType::new(BirthmarkType::OpKgramSeq(4), Algorithm::Levenshtein),
×
466
            Analysis::Op4gramSeqLcs => AnalysisType::new(BirthmarkType::OpKgramSeq(4), Algorithm::Lcs),
×
467
            Analysis::Op4gramFreqCosine => AnalysisType::new(BirthmarkType::OpKgramFreq(4), Algorithm::Cosine),
×
468
            Analysis::Op4gramFreqEuclidean => AnalysisType::new(BirthmarkType::OpKgramFreq(4), Algorithm::Euclidean),
×
469
            Analysis::Op4gramFreqWeightedjaccard => AnalysisType::new(BirthmarkType::OpKgramFreq(4), Algorithm::WeightedJaccard),
×
470

471
            Analysis::Op5gramSetDice => AnalysisType::new(BirthmarkType::OpKgramSet(5), Algorithm::Dice),
×
472
            Analysis::Op5gramSetJaccard => AnalysisType::new(BirthmarkType::OpKgramSet(5), Algorithm::Jaccard),
×
473
            Analysis::Op5gramSetSimpson => AnalysisType::new(BirthmarkType::OpKgramSet(5), Algorithm::Simpson),
×
474
            Analysis::Op5gramSeqLevenshtein => AnalysisType::new(BirthmarkType::OpKgramSeq(5), Algorithm::Levenshtein),
×
475
            Analysis::Op5gramSeqLcs => AnalysisType::new(BirthmarkType::OpKgramSeq(5), Algorithm::Lcs),
×
476
            Analysis::Op5gramFreqCosine => AnalysisType::new(BirthmarkType::OpKgramFreq(5), Algorithm::Cosine),
×
477
            Analysis::Op5gramFreqEuclidean => AnalysisType::new(BirthmarkType::OpKgramFreq(5), Algorithm::Euclidean),
×
478
            Analysis::Op5gramFreqWeightedjaccard => AnalysisType::new(BirthmarkType::OpKgramFreq(5), Algorithm::WeightedJaccard),
×
479

480
            Analysis::Op6gramSetDice => AnalysisType::new(BirthmarkType::OpKgramSet(6), Algorithm::Dice),
×
481
            Analysis::Op6gramSetJaccard => AnalysisType::new(BirthmarkType::OpKgramSet(6), Algorithm::Jaccard),
×
482
            Analysis::Op6gramSetSimpson => AnalysisType::new(BirthmarkType::OpKgramSet(6), Algorithm::Simpson),
×
483
            Analysis::Op6gramSeqLevenshtein => AnalysisType::new(BirthmarkType::OpKgramSeq(6), Algorithm::Levenshtein),
×
484
            Analysis::Op6gramSeqLcs => AnalysisType::new(BirthmarkType::OpKgramSeq(6), Algorithm::Lcs),
×
485
            Analysis::Op6gramFreqCosine => AnalysisType::new(BirthmarkType::OpKgramFreq(6), Algorithm::Cosine),
×
486
            Analysis::Op6gramFreqEuclidean => AnalysisType::new(BirthmarkType::OpKgramFreq(6), Algorithm::Euclidean),
×
487
            Analysis::Op6gramFreqWeightedjaccard => AnalysisType::new(BirthmarkType::OpKgramFreq(6), Algorithm::WeightedJaccard),
×
488

489
        }
490
    }
1✔
491
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc