Karolos M. Grigoriadis

112 papers A* 1A 1B 2C 51Journal 33Unranked 24
YearRankTypeTitle / Venue / Authors
2024 J jnl
IEEE Access
Malek Rekik, Omar Khaled, Karolos M. Grigoriadis, Matthew A. Franchek
2023 J jnl
IEEE Trans. Ind. Informatics
Zhaowen Feng, Guoyan Cao, Karolos M. Grigoriadis, Quan Pan
2022 J jnl
Int. J. Syst. Sci.
Saeed Salavati Dezfuli, Karolos M. Grigoriadis
2021 C conf
ACC
Saeed Salavati Dezfuli, Karolos M. Grigoriadis, Matthew A. Franchek
2021 J jnl
Int. J. Control
Guoyan Cao, Karolos M. Grigoriadis
2021 J jnl
Int. J. Syst. Sci.
Guoyan Cao, Karolos M. Grigoriadis, Puchen Liu
2021 C conf
ACC
Shahin Tasoujian, Saeed Salavati Dezfuli, Karolos M. Grigoriadis, Matthew A. Franchek
2020 C conf
ACC
Shahin Tasoujian, Saeed Salavati Dezfuli, Karolos M. Grigoriadis, Matthew A. Franchek
2018 B conf
SMC
Guoyan Cao, Feisheng Yang, Karolos M. Grigoriadis
2018 J jnl
Int. J. Control
Gabriela Werner Gabriel, José Claudio Geromel, Karolos M. Grigoriadis
2017 conf
ICCMS
Guoyan Cao, Tamás Luspay, Behrouz Ebrahimi, Karolos M. Grigoriadis, Matthew A. Franchek, Nicole Marques, Jordan Wolf, George C. Kramer
2016 J jnl
IEEE Trans. Control. Syst. Technol.
Tamás Luspay, Karolos M. Grigoriadis
2016 J jnl
J. Comput. Sci.
Morteza Mohammadzaheri, Reza Tafreshi, Zurwa Khan, Matthew A. Franchek, Karolos M. Grigoriadis
2016 J jnl
J. Intell. Transp. Syst.
Guoyan Cao, John Michelini, Karolos M. Grigoriadis, Behrouz Ebrahimi, Matthew A. Franchek
2016 C conf
ACC
Saeed Salavati Dezfuli, Behrouz Ebrahimi, Karolos M. Grigoriadis, Matthew A. Franchek
2016 conf
AIM
Ala E. Omrani, Matthew A. Franchek, Karolos M. Grigoriadis, Reza Tafreshi
2016 conf
ECC
Gabriela Werner Gabriel, José Claudio Geromel, Karolos M. Grigoriadis
2015 C conf
ACC
Amin Ramezanifar, Javad Mohammadpour, Karolos M. Grigoriadis
2015 conf
SoSE
Guoyan Cao, John Michelini, Karolos M. Grigoriadis, Behrouz Ebrahimi, Matthew A. Franchek
2015 C conf
ACC
Matthew A. Franchek, Behrouz Ebrahimi, Karolos M. Grigoriadis, Imad H. Makki
2015 J jnl
Int. J. Control
Tamás Luspay, Karolos M. Grigoriadis
2015 J jnl
Int. J. Syst. Sci.
Amin Ramezanifar, Javad Mohammadpour, Karolos M. Grigoriadis
2014 J jnl
Int. J. Control
Behrouz Ebrahimi, Reza Tafreshi, Matthew A. Franchek, Karolos M. Grigoriadis, Javad Mohammadpour
2014 C conf
ACC
Tamás Luspay, Karolos M. Grigoriadis
2014 conf
BIOSTEC (Selected Papers)
Cecília Lantos, Rafik Borji, Stéphane Douady, Karolos M. Grigoriadis, Kirill V. Larin, Matthew A. Franchek
2014 conf
BIOIMAGING
Cecília Lantos, Rafik Borji, Stéphane Douady, Karolos M. Grigoriadis, Kirill V. Larin, Matthew A. Franchek
2014 J jnl
Int. J. Control
Amin Ramezanifar, Javad Mohammadpour, Karolos M. Grigoriadis
2014 J jnl
IEEE Trans. Control. Syst. Technol.
Behrouz Ebrahimi, Reza Tafreshi, Javad Mohammadpour, Matthew A. Franchek, Karolos M. Grigoriadis, Houshang Masudi
2014 C conf
ACC
Behrouz Ebrahimi, Reza Tafreshi, Javad Mohammadpour, Matthew A. Franchek, Karolos M. Grigoriadis
2014 C conf
ACC
Fatemeh Zamanian, Matthew A. Franchek, Karolos M. Grigoriadis, Imad H. Makki
2013 C conf
ACC
Amirhossein Salimi, Amin Ramezanifar, Javad Mohammadpour, Karolos M. Grigoriadis
2013 A* conf
ICRA
Nicholas C. von Sternberg, Atilla Kilicarslan, Nikhil V. Navkar, Zhigang Deng, Karolos M. Grigoriadis, Nikolaos V. Tsekos
2013 C conf
ACC
Tamás Luspay, Balázs Kulcsár, Karolos M. Grigoriadis
2013 C conf
ACC
Tamás Luspay, Karolos M. Grigoriadis, Matthew A. Franchek
2013 C conf
ACC
Amin Ramezanifar, Javad Mohammadpour, Karolos M. Grigoriadis
2012 C conf
ACC
Farzad A. Shirazi, Karolos M. Grigoriadis, Daniel E. Viassolo
2012 C conf
ACC
Mona Meisami-Azad, Javad Mohammadpour, Karolos M. Grigoriadis
2012 C conf
ACC
Rohit Zope, Javad Mohammadpour, Karolos M. Grigoriadis, Matthew A. Franchek
2012 J jnl
IEEE Trans. Control. Syst. Technol.
Farzad A. Shirazi, Javad Mohammadpour, Karolos M. Grigoriadis, Gangbing Song
2012 J jnl
Int. J. Control
Mona Meisami-Azad, Javad Mohammadpour, Karolos M. Grigoriadis, Michael P. Harold, Matthew A. Franchek
2012 conf
CDC
Amin Ramezanifar, Javad Mohammadpour, Karolos M. Grigoriadis
2012 C conf
ACC
Amin Ramezanifar, Javad Mohammadpour, Karolos M. Grigoriadis
2012 C conf
ACC
Halim Alwi, Argyrios C. Zolotas, Christopher Edwards, Karolos M. Grigoriadis
2012 conf
ISGT
Ali Hooshmand, Mohammad H. Poursaeidi, Javad Mohammadpour, Heidar A. Malki, Karolos M. Grigoriadis
2012 J jnl
Int. J. Control
Farzad A. Shirazi, Karolos M. Grigoriadis, Daniel E. Viassolo
2011 C conf
ACC
Javad Mohammadpour, Matthew A. Franchek, Karolos M. Grigoriadis
2011 J jnl
Int. J. Control
Javad Mohammadpour, Karolos M. Grigoriadis, Matthew A. Franchek, Yue-Yun Wang, Ibrahim Haskara
2011 C conf
ACC
Aniket Gupta, Matthew A. Franchek, Karolos M. Grigoriadis, Daniel J. Smith
2011 C conf
ACC
Rohit Zope, Matthew A. Franchek, Karolos M. Grigoriadis, Gopi Chandra Surnilla, Stephen Smith
2011 C conf
ACC
Mona Meisami-Azad, Javad Mohammadpour, Karolos M. Grigoriadis, Michael P. Harold, Matthew A. Franchek
2011 C conf
ACC
Farzad A. Shirazi, Karolos M. Grigoriadis, Gangbing Song
2010 C conf
ACC
Mona Meisami-Azad, Javad Mohammadpour, Karolos M. Grigoriadis, Michael P. Harold
2010 C conf
ACC
Farzad A. Shirazi, Javad Mohammadpour, Karolos M. Grigoriadis
2010 C conf
ACC
S. Mirheidari, Javad Mohammadpour, Karolos M. Grigoriadis, Matthew A. Franchek
2010 C conf
ACC
Javad Mohammadpour, Karolos M. Grigoriadis, Matthew A. Franchek, Yue-Yun Wang, Ibrahim Haskara
2010 C conf
ACC
Rohit Zope, Javad Mohammadpour, Karolos M. Grigoriadis, Matthew A. Franchek
2009 J jnl
Autom.
Mona Meisami-Azad, Javad Mohammadpour, Karolos M. Grigoriadis
2009 conf
CDC
Kazuhiko Hiramoto, Javad Mohammadpour, Karolos M. Grigoriadis
2009 C conf
ACC
Javad Mohammadpour, Karolos M. Grigoriadis, Matthew A. Franchek, Yue-Yun Wang
2009 C conf
ACC
Mona Meisami-Azad, Javad Mohammadpour, Karolos M. Grigoriadis
2008 J jnl
Int. J. Control
Charudatta S. Mehendale, Karolos M. Grigoriadis
2008 conf
CDC
Javad Mohammadpour, Mona Meisami-Azad, Karolos M. Grigoriadis
2008 J jnl
Syst. Control. Lett.
Javad Mohammadpour Velni, Karolos M. Grigoriadis
2008 C conf
ACC
Mona Meisami-Azad, Javad Mohammadpour, Karolos M. Grigoriadis
2008 conf
CDC
Mona Meisami-Azad, Karolos M. Grigoriadis
2008 J jnl
IEEE Trans. Circuits Syst. I Regul. Pap.
Javad Mohammadpour Velni, Karolos M. Grigoriadis
2008 C conf
ACC
Javad Mohammadpour, Karolos M. Grigoriadis, Matthew A. Franchek, Benjamin J. Zwissler
2008 J jnl
Int. J. Model. Identif. Control.
Feng Zhang, Karolos M. Grigoriadis, Matthew A. Franchek, Imad H. Makki
2007 C conf
ACC
Raffaele Potami, Michael A. Demetriou, Karolos M. Grigoriadis
2007 C conf
ACC
Javad Mohammadpour, Mona Meisami-Azad, Karolos M. Grigoriadis
2007 C conf
ACC
Mona Meisami-Azad, Javad Mohammadpour, Karolos M. Grigoriadis
2007 J jnl
Int. J. Control
Javad Mohammadpour Velni, Karolos M. Grigoriadis
2007 conf
CDC
Javad Mohammadpour, Karolos M. Grigoriadis
2007 J jnl
J. Frankl. Inst.
Kan Tan, Karolos M. Grigoriadis
2006 conf
CDC
Yuanqiang Bai, Karolos M. Grigoriadis, Michael A. Demetriou
2006 C conf
ACC
Javad Mohammadpour, Karolos M. Grigoriadis
2006 conf
CDC
Yuanqiang Bai, Karolos M. Grigoriadis
2006 C conf
ACC
Charudatta S. Mehendale, Karolos M. Grigoriadis
2006 conf
CDC
Javad Mohammadpour, Karolos M. Grigoriadis
2006 C conf
ACC
Yuanqiang Bai, Karolos M. Grigoriadis, Vineet Sethi
2006 C conf
ACC
Feng Zhang, Karolos M. Grigoriadis, Matthew A. Franchek, Imad H. Makki
2005 conf
ISIC
Javad Mohammadpour Velni, Karolos M. Grigoriadis
2005 conf
ISIC
Feng Zhang, Karolos M. Grigoriadis
2005 B conf
SMC
Javad Mohammadpour Velni, Karolos M. Grigoriadis
2005 C conf
ACC
Robert Sweeney, Michael A. Demetriou, Karolos M. Grigoriadis
2005 conf
ISIC
Robert Sweeney, Michael A. Demetriou, Karolos M. Grigoriadis
2005 C conf
ACC
Kazuhiko Hiramoto, Karolos M. Grigoriadis
2005 conf
CDC/ECC
Feng Zhang, Karolos M. Grigoriadis, Matthew A. Franchek, Imad H. Makki
2004 conf
CDC
Charudatta S. Mehendale, Karolos M. Grigoriadis
2004 C conf
ACC
Michael A. Demetriou, Karolos M. Grigoriadis
2004 C conf
ACC
Yuanqiang Bai, Karolos M. Grigoriadis, Michael A. Demetriou
2004 C conf
ACC
Charudatta S. Mehendale, Karolos M. Grigoriadis
2003 C conf
ACC
Charudatta S. Mehendale, Karolos M. Grigoriadis
2003 C conf
ACC
Charudatta S. Mehendale, Ian J. Fialho, Karolos M. Grigoriadis
2003 C conf
ACC
Qingwen Song, Karolos M. Grigoriadis
2001 A conf
MICCAI
Ioannis A. Kakadiaris, Amol Pednekar, G. Zouridakis, Karolos M. Grigoriadis
2001 C conf
ACC
M. O. Abdalla, E. G. Nobrega, Karolos M. Grigoriadis
2001 J jnl
Autom.
Fen Wu, Karolos M. Grigoriadis
2001 conf
CDC
Kan Tan, Karolos M. Grigoriadis
2001 conf
ECC
Kan Tan, Karolos M. Grigoriadis
2001 J jnl
Syst. Control. Lett.
Kan Tan, Karolos M. Grigoriadis
2000 conf
CDC
Kan Tan, Karolos M. Grigoriadis
2000 conf
CDC
E. G. Nobrega, M. O. Abdalla, Karolos M. Grigoriadis
2000 C conf
ACC
Eurípedes G. O. Nóbrega, Marco Antonio de Oliveira Alves, Karolos M. Grigoriadis
2000 C conf
ACC
Kan Tan, Karolos M. Grigoriadis, Fen Wu
2000 C conf
ACC
Musa O. Abdallat, David C. Zimmermant, Karolos M. Grigoriadis
1997 J jnl
Autom.
Karolos M. Grigoriadis, Robert E. Skelton
1997 J jnl
Autom.
Vassilis L. Syrmos, Chaouki T. Abdallah, Peter Dorato, Karolos M. Grigoriadis
1996 J jnl
Autom.
Karolos M. Grigoriadis, Robert E. Skelton
1995 J jnl
IEEE Trans. Autom. Control.
Arthur E. Frazho, Karolos M. Grigoriadis, S. M. Kherat
1994 J jnl
IEEE Trans. Signal Process.
Karolos M. Grigoriadis, Arthur E. Frazho, Robert E. Skelton
1993 J jnl
Autom.
Kazunori Yasuda, Robert E. Skelton, Karolos M. Grigoriadis
redb/extractors/js_extractors/js_patterns.py
← Index redb/extractors/js_extractors/js_patterns.py python
"""Canonical, compiled JavaScript regex patterns shared across JS extractors.

All suspicious-API patterns and the few feature-only patterns live here so each
expression is compiled exactly once per Python process and so any pattern that
was previously duplicated across `js_features.py` and `js_suspicious_apis.py`
now resolves to a single shared compiled object.

JavaScript is case-sensitive at runtime, but every suspicious-API pattern matches
either a literal-case identifier (`\\beval\\s*\\(`, `String\\.fromCharCode`, etc.)
or a string-quoted token (`"powershell"`). Compiling them with `re.IGNORECASE`
matches the historical behaviour of `JSSuspiciousAPIsExtractor` and is safe for
the patterns that historically came from `JSFeaturesExtractor` — those literals
are spelled in real-world JS exactly as written.

`scan_source()` is the entry point used by extractors: it walks the source once
per pattern using the pre-compiled regexes and returns a flat
`{name: {"count": N, "lines": [unique_line_numbers_sorted]}}` dict. Both
`JSFeaturesExtractor` and `JSSuspiciousAPIsExtractor` consume the same dict so
the per-pattern × per-line loops they used to run independently collapse to a
single shared scan.
"""

import bisect
import re
from typing import Dict, Iterable, List, Mapping

_FLAGS = re.IGNORECASE

# Canonical compiled patterns, keyed by their human-readable name. The name is
# also the value emitted into `redb_js_suspicious_apis.api_name`.
PATTERNS = {
    # ---- code execution ----
    "eval": re.compile(r"\beval\s*\(", _FLAGS),
    "Function constructor": re.compile(r"\bnew\s+Function\s*\(", _FLAGS),
    "execScript": re.compile(r"\bexecScript\s*\(", _FLAGS),
    "document.write": re.compile(r"\bdocument\.write(?:ln)?\s*\(", _FLAGS),
    "innerHTML assignment": re.compile(r"\.innerHTML\s*=", _FLAGS),
    "outerHTML assignment": re.compile(r"\.outerHTML\s*=", _FLAGS),
    "insertAdjacentHTML": re.compile(r"\.insertAdjacentHTML\s*\(", _FLAGS),
    # ---- network ----
    "XMLHttpRequest": re.compile(r"\bnew\s+XMLHttpRequest\b", _FLAGS),
    "fetch": re.compile(r"\bfetch\s*\(", _FLAGS),
    "WebSocket": re.compile(r"\bnew\s+WebSocket\s*\(", _FLAGS),
    "navigator.sendBeacon": re.compile(r"\bnavigator\.sendBeacon\s*\(", _FLAGS),
    "ActiveXObject XMLHTTP": re.compile(
        r"ActiveXObject\s*\(\s*[\"\'](?:MSXML2\.XMLHTTP|Microsoft\.XMLHTTP)", _FLAGS
    ),
    "require network module": re.compile(
        r"require\s*\(\s*[\"\'](?:http|https|net|dgram)[\"\']", _FLAGS
    ),
    "axios": re.compile(r"\baxios\b", _FLAGS),
    # ---- filesystem ----
    "require fs": re.compile(r"require\s*\(\s*[\"\']fs[\"\']", _FLAGS),
    "require path": re.compile(r"require\s*\(\s*[\"\']path[\"\']", _FLAGS),
    "FileSystemObject": re.compile(r"Scripting\.FileSystemObject", _FLAGS),
    "ADODB.Stream": re.compile(r"ADODB\.Stream", _FLAGS),
    "Shell.Application": re.compile(r"Shell\.Application", _FLAGS),
    "WScript.CreateObject": re.compile(r"WScript\.CreateObject", _FLAGS),
    # ---- process ----
    "require child_process": re.compile(r"require\s*\(\s*[\"\']child_process[\"\']", _FLAGS),
    "child_process exec": re.compile(r"child_process\.(?:exec|spawn|execFile|fork)\s*\(", _FLAGS),
    "WScript.Shell": re.compile(r"WScript\.Shell", _FLAGS),
    "WScript.Shell.Run": re.compile(r"\.Run\s*\(", _FLAGS),
    "WScript.Shell.Exec": re.compile(r"\.Exec\s*\(", _FLAGS),
    "ShellExecute": re.compile(r"\bShellExecute\b", _FLAGS),
    "PowerShell reference": re.compile(r"[\"\']powershell[\"\']", _FLAGS),
    "cmd.exe reference": re.compile(r"[\"\']cmd\.exe[\"\']", _FLAGS),
    "require os": re.compile(r"require\s*\(\s*[\"\']os[\"\']", _FLAGS),
    # ---- registry ----
    "RegRead": re.compile(r"\.RegRead\s*\(", _FLAGS),
    "RegWrite": re.compile(r"\.RegWrite\s*\(", _FLAGS),
    "RegDelete": re.compile(r"\.RegDelete\s*\(", _FLAGS),
    "StdRegProv": re.compile(r"StdRegProv", _FLAGS),
    # ---- crypto / encoding ----
    "atob": re.compile(r"\batob\s*\(", _FLAGS),
    "btoa": re.compile(r"\bbtoa\s*\(", _FLAGS),
    "String.fromCharCode": re.compile(r"String\.fromCharCode\s*\(", _FLAGS),
    "unescape": re.compile(r"\bunescape\s*\(", _FLAGS),
    "decodeURIComponent": re.compile(r"\bdecodeURIComponent\s*\(", _FLAGS),
    "Buffer.from": re.compile(r"Buffer\.from\s*\(", _FLAGS),
    "crypto module": re.compile(r"crypto\.create(?:Cipher|Decipher|Hash|Hmac)", _FLAGS),
    # ---- DOM manipulation ----
    "document.forms": re.compile(r"document\.forms", _FLAGS),
    "document.cookie": re.compile(r"document\.cookie", _FLAGS),
    "querySelector sensitive input": re.compile(
        r"document\.querySelector\s*\([^)]*(?:password|credit|card|cvv|ssn)", _FLAGS
    ),
    "submit event listener": re.compile(r"addEventListener\s*\(\s*[\"\']submit", _FLAGS),
    "createElement script/iframe": re.compile(
        r"\.createElement\s*\(\s*[\"\'](?:script|iframe)", _FLAGS
    ),
    "dynamic script src": re.compile(r"\.src\s*=\s*[\"\'](?:https?://|//)", _FLAGS),
}

# Pattern name -> category (one of code_execution / network / filesystem /
# process / registry / crypto_encoding / dom_manipulation).
CATEGORIES = {
    "eval": "code_execution",
    "Function constructor": "code_execution",
    "execScript": "code_execution",
    "document.write": "code_execution",
    "innerHTML assignment": "code_execution",
    "outerHTML assignment": "code_execution",
    "insertAdjacentHTML": "code_execution",
    "XMLHttpRequest": "network",
    "fetch": "network",
    "WebSocket": "network",
    "navigator.sendBeacon": "network",
    "ActiveXObject XMLHTTP": "network",
    "require network module": "network",
    "axios": "network",
    "require fs": "filesystem",
    "require path": "filesystem",
    "FileSystemObject": "filesystem",
    "ADODB.Stream": "filesystem",
    "Shell.Application": "filesystem",
    "WScript.CreateObject": "filesystem",
    "require child_process": "process",
    "child_process exec": "process",
    "WScript.Shell": "process",
    "WScript.Shell.Run": "process",
    "WScript.Shell.Exec": "process",
    "ShellExecute": "process",
    "PowerShell reference": "process",
    "cmd.exe reference": "process",
    "require os": "process",
    "RegRead": "registry",
    "RegWrite": "registry",
    "RegDelete": "registry",
    "StdRegProv": "registry",
    "atob": "crypto_encoding",
    "btoa": "crypto_encoding",
    "String.fromCharCode": "crypto_encoding",
    "unescape": "crypto_encoding",
    "decodeURIComponent": "crypto_encoding",
    "Buffer.from": "crypto_encoding",
    "crypto module": "crypto_encoding",
    "document.forms": "dom_manipulation",
    "document.cookie": "dom_manipulation",
    "querySelector sensitive input": "dom_manipulation",
    "submit event listener": "dom_manipulation",
    "createElement script/iframe": "dom_manipulation",
    "dynamic script src": "dom_manipulation",
}

# Patterns consumed only by JSFeaturesExtractor (no category, never surfaced as
# a suspicious-API row). Kept here so every JS regex is compiled in one place.
FEATURE_PATTERNS = {
    "hex_escape": re.compile(r"\\x[0-9a-fA-F]{2}"),
    "unicode_escape": re.compile(r"\\u[0-9a-fA-F]{4}"),
    "base64_string": re.compile(r"[A-Za-z0-9+/]{40,}={0,2}"),
    # decodeURI matches BOTH decodeURI and decodeURIComponent. The latter is also
    # a suspicious-API pattern in PATTERNS; this broader form is what the
    # `decodeuri_count` feature column has historically counted.
    "decodeURI": re.compile(r"\b(?:decodeURI|decodeURIComponent)\s*\(", _FLAGS),
    "settimeout_setinterval": re.compile(r"\b(?:setTimeout|setInterval)\s*\(", _FLAGS),
    "function_decl": re.compile(r"\bfunction\s+\w+\s*\(|\bfunction\s*\("),
    "var_decl": re.compile(r"\b(?:var|let|const)\s+"),
    "string_concat": re.compile(r"[\"\'][\s]*\+[\s]*[\"\']"),
    "comment": re.compile(r"//.*?$|/\*[\s\S]*?\*/", re.MULTILINE),
    "long_string": re.compile(r"[\"\']([^\"\']{256,})[\"\']"),
    "array_function_call": re.compile(r"\[(?:0x[0-9a-f]+|[\d]+)\]\s*\(", _FLAGS),
}

# Patterns consumed only by JSStringsExtractor for encoded-string discovery.
# Scoped to *hidden* strings only — patterns whose decoded form is not visible
# to a substring search over the raw text. Plain long literals are not
# extracted here because they're already preserved in code_text_content and
# scraped by the IOC pipeline over text_raw / text_normalized.
#
# Distinct from FEATURE_PATTERNS even where the names rhyme:
#   FEATURE_PATTERNS["hex_escape"] / ["unicode_escape"]   -> single escape
#   STRING_PATTERNS["hex_escape_seq"] / ["unicode_escape_seq"] -> 4+ / 3+ in a row
#   FEATURE_PATTERNS["base64_string"]                     -> bare base64 token
#   STRING_PATTERNS["base64_quoted"]                      -> base64 inside JS quotes
# These do not share match objects with the suspicious-API or feature scans, so
# they are not folded into JSContext.scan; the strings extractor walks them
# itself (one finditer per pattern, with shared line-offset bisect in #4b).
STRING_PATTERNS = {
    "hex_escape_seq": re.compile(r"(?:\\x[0-9a-fA-F]{2}){4,}"),
    "unicode_escape_seq": re.compile(r"(?:\\u[0-9a-fA-F]{4}){3,}"),
    "charcode_call": re.compile(r"String\.fromCharCode\s*\(\s*([\d,\s]+)\s*\)"),
    "base64_quoted": re.compile(r"[\"\']([A-Za-z0-9+/]{40,}={0,2})[\"\']"),
    "concat_chain": re.compile(r"(?:[\"\'][^\"\']+[\"\']\s*\+\s*){3,}[\"\'][^\"\']+[\"\']"),
}


def line_offsets(source: str) -> List[int]:
    """Sorted list of byte offsets for every newline in `source`, plus a final
    sentinel of len(source). Used to translate match offsets into 1-indexed
    line numbers via bisect.
    """
    offsets = [-1]  # so that bisect_right of offset 0 returns line 1
    push = offsets.append
    idx = source.find("\n")
    while idx != -1:
        push(idx)
        idx = source.find("\n", idx + 1)
    return offsets


def _scan_one(
    pattern: "re.Pattern[str]", source: str, offsets: List[int]
) -> Dict[str, object]:
    """Run a single compiled pattern over `source` and return count + unique lines."""
    count = 0
    seen_lines: "set[int]" = set()
    for m in pattern.finditer(source):
        count += 1
        seen_lines.add(bisect.bisect_right(offsets, m.start()))
    if not count:
        return None  # type: ignore[return-value]
    return {"count": count, "lines": sorted(seen_lines)}


def scan_source(
    source: str,
    patterns: Iterable[Mapping[str, "re.Pattern[str]"]] = (PATTERNS, FEATURE_PATTERNS),
) -> Dict[str, Dict[str, object]]:
    """Scan `source` against every compiled pattern in `patterns`.

    Returns a dict keyed by pattern name. Each entry has:
        "count": total number of matches in the source
        "lines": sorted list of unique 1-indexed line numbers where the pattern
                 matched (deduplicated — multiple matches on the same line
                 collapse to one entry, preserving the historical
                 line-set semantics of JSSuspiciousAPIsExtractor)
    Patterns with zero matches are absent from the dict; callers should default
    to {"count": 0, "lines": []}.
    """
    if not source:
        return {}
    offsets = line_offsets(source)
    results: Dict[str, Dict[str, object]] = {}
    for table in patterns:
        for name, pat in table.items():
            entry = _scan_one(pat, source, offsets)
            if entry is not None:
                results[name] = entry
    return results