mirror of
https://github.com/morluto/rea.git
synced 2026-11-09 16:26:36 +00:00
* fix(native): find procedures by the secondary names exact lookup accepts search_procedures matched only each procedure's primary name, while exact lookup also accepts Kuna inventory aliases (versioned-symbol and generated sub_ names) and Ghidra symbols at an exact function entry. A search for a name procedure_address resolves returned nothing, and trace_feature silently missed the procedure. Both providers now match the same names their exact lookup accepts. A procedure still appears once under its primary name; the optional matched_aliases field lists the secondary names that matched, so aliases never become procedures of their own and list_procedures is unchanged. The shared domain helper owns that row rule for Kuna and for the Ghidra host-side Unicode folding pass. The Ghidra analysis profile records the new search semantics so older snapshots cannot replay alias-blind results. Fixes #8330 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(ghidra): match leaf spellings of procedure names in search procedure_address accepts each entry symbol's leaf or namespace-qualified spelling, but procedure search tested only qualified names, so a regex such as ^audit_alias$ missed a secondary symbol ns::audit_alias (and ^same$ missed a primary alpha::same) that exact lookup selects. Lookup and search now share one bridge definition of the names that select a procedure, so they cannot drift. Matching secondary symbols are still reported by their qualified names; because a leaf ends its qualified spelling, the host's Unicode literal pass stays exact over those names. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
67 lines
2.5 KiB
TypeScript
67 lines
2.5 KiB
TypeScript
import { z } from "zod";
|
|
|
|
import { nativeSearchInput } from "./native/nativeSearch.js";
|
|
import { AnalysisInputError } from "./analysisErrorCore.js";
|
|
|
|
/** Parsed `search_procedures`/`search_strings` query shared by providers that match in REA. */
|
|
export const analysisSearchQuerySchema = z.object(nativeSearchInput);
|
|
|
|
export type AnalysisSearchQuery = z.infer<typeof analysisSearchQuerySchema>;
|
|
|
|
/**
|
|
* Match a literal substring. With case ignored, ECMAScript Unicode RegExp
|
|
* matching applies simple case folding character-by-character
|
|
* ([Canonicalize](https://tc39.es/ecma262/2023/multipage/text-processing.html#sec-runtime-semantics-canonicalize));
|
|
* it is locale-independent and does not expand characters into sequences.
|
|
*/
|
|
export const literalSubstringPredicate = (
|
|
pattern: string,
|
|
caseSensitive: boolean,
|
|
): ((value: string) => boolean) => {
|
|
if (caseSensitive) return (value) => value.includes(pattern);
|
|
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
const regex = new RegExp(escaped, "iu");
|
|
return (value) => regex.test(value);
|
|
};
|
|
|
|
/**
|
|
* Match names the way the search contract describes: a literal substring by
|
|
* default, or a Unicode regular expression, case-insensitive unless requested.
|
|
* An invalid pattern is the caller's input error.
|
|
*/
|
|
export const analysisSearchPredicate = (
|
|
query: AnalysisSearchQuery,
|
|
): ((value: string) => boolean) => {
|
|
if (query.mode === "regex") {
|
|
let regex: RegExp;
|
|
try {
|
|
regex = new RegExp(query.pattern, query.case_sensitive ? "u" : "iu");
|
|
} catch (cause: unknown) {
|
|
throw new AnalysisInputError("search", { cause });
|
|
}
|
|
return (value) => regex.test(value);
|
|
}
|
|
return literalSubstringPredicate(query.pattern, query.case_sensitive);
|
|
};
|
|
|
|
/**
|
|
* Match one procedure by its primary name and the secondary names its provider
|
|
* accepts for exact selection. A procedure remains one row whose value is its
|
|
* primary name; matched_aliases keeps each distinct matching secondary
|
|
* spelling in provider order, so an alias never becomes a procedure of its own.
|
|
*/
|
|
export const matchProcedureNames = <Row extends { readonly value: string }>(
|
|
row: Row,
|
|
aliases: readonly string[],
|
|
matches: (value: string) => boolean,
|
|
): (Row & { readonly matched_aliases?: string[] }) | undefined => {
|
|
const matched =
|
|
aliases.length === 0
|
|
? []
|
|
: [...new Set(aliases)].filter(
|
|
(alias) => alias !== row.value && matches(alias),
|
|
);
|
|
if (matched.length > 0) return { ...row, matched_aliases: matched };
|
|
return matches(row.value) ? row : undefined;
|
|
};
|