Skip to content

Commit f990aa7

Browse files
authored
Merge pull request #5629 from RaiKoHoff/Dev_Master
fix: grepWin call parameter issues
2 parents 32d51e4 + 771dbf9 commit f990aa7

7 files changed

Lines changed: 208 additions & 74 deletions

File tree

.github/copilot-instructions.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,3 +236,19 @@ Use `Path_StrgComparePath()` for comparing file paths — it supports optional n
236236
### Undo/Redo transactions
237237
238238
Use `_BEGIN_UNDO_ACTION_` / `_END_UNDO_ACTION_` macros (defined in `Notepad3.h`) to group Scintilla operations into single undo steps. These also handle notification limiting during bulk edits.
239+
240+
### WriteAccessBuf — Dangling Pointer Anti-Pattern
241+
242+
**NEVER use a pointer obtained from `Path_WriteAccessBuf()` / `StrgWriteAccessBuf()` after ANY operation that may reallocate or swap the underlying buffer of the SAME handle.** The pointer becomes dangling (use-after-free).
243+
244+
Buffer-invalidating operations:
245+
- `Path_CanonicalizeEx(h, ...)` — calls `Path_Swap` internally
246+
- `Path_Swap(h, ...)` / `StrgSwap(h, ...)`
247+
- `Path_ExpandEnvStrings(h)` / `ExpandEnvironmentStrgs(h, ...)` — may realloc
248+
- `Path_Append(h, ...)` / `Path_Reset(h, ...)` — may realloc
249+
- `StrgCat(h, ...)` / `StrgInsert(h, ...)` / `StrgFormat(h, ...)` / `StrgReset(h, ...)` — may realloc
250+
- `Path_NormalizeEx(h, ...)` / `Path_AbsoluteFromApp(h, ...)` / `Path_RelativeToApp(h, ...)` — may realloc/swap
251+
252+
Safe patterns after invalidation:
253+
- **Read-only**: use `Path_Get(h)` or `StrgGet(h)` — always returns current buffer
254+
- **Read-write**: re-obtain via `ptr = Path_WriteAccessBuf(h, 0)` / `ptr = StrgWriteAccessBuf(h, 0)` (size 0 = no resize, just returns current pointer)

CLAUDE.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,22 @@ Application state is centralized in global structs (`Globals`, `Settings`, `Sett
229229
230230
Use `_BEGIN_UNDO_ACTION_` / `_END_UNDO_ACTION_` macros (defined in `Notepad3.h`) to group Scintilla operations into single undo steps. These also handle notification limiting during bulk edits.
231231
232+
### WriteAccessBuf — Dangling Pointer Anti-Pattern
233+
234+
**NEVER use a pointer obtained from `Path_WriteAccessBuf()` / `StrgWriteAccessBuf()` after ANY operation that may reallocate or swap the underlying buffer of the SAME handle.** The pointer becomes dangling (use-after-free).
235+
236+
Buffer-invalidating operations:
237+
- `Path_CanonicalizeEx(h, ...)` — calls `Path_Swap` internally
238+
- `Path_Swap(h, ...)` / `StrgSwap(h, ...)`
239+
- `Path_ExpandEnvStrings(h)` / `ExpandEnvironmentStrgs(h, ...)` — may realloc
240+
- `Path_Append(h, ...)` / `Path_Reset(h, ...)` — may realloc
241+
- `StrgCat(h, ...)` / `StrgInsert(h, ...)` / `StrgFormat(h, ...)` / `StrgReset(h, ...)` — may realloc
242+
- `Path_NormalizeEx(h, ...)` / `Path_AbsoluteFromApp(h, ...)` / `Path_RelativeToApp(h, ...)` — may realloc/swap
243+
244+
Safe patterns after invalidation:
245+
- **Read-only**: use `Path_Get(h)` or `StrgGet(h)` — always returns current buffer
246+
- **Read-write**: re-obtain via `ptr = Path_WriteAccessBuf(h, 0)` / `ptr = StrgWriteAccessBuf(h, 0)` (size 0 = no resize, just returns current pointer)
247+
232248
## Python Environment
233249
234250
A Python 3.14 virtual environment is available at `.venv\` for scripting tasks (batch file manipulation, locale file updates, code generation, etc.).

np3portableapp/Notepad3Portable/App/AppInfo/appinfo.ini

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ CommercialUse=true
2323
;EULAVersion=1
2424

2525
[Version]
26-
PackageVersion=7.26.402.7
27-
DisplayVersion=7.26.402.7_DEV
26+
PackageVersion=7.26.402.12
27+
DisplayVersion=7.26.402.12_DEV
2828

2929
[SpecialPaths]
3030
Plugins=NONE

src/Dialogs.c

Lines changed: 100 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1394,9 +1394,9 @@ static INT_PTR CALLBACK RunDlgProc(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM l
13941394

13951395
GetDlgItemText(hwnd, IDC_COMMANDLINE, args_buf, (int)StrgGetAllocLength(hargs_str));
13961396
StrgSanitize(hargs_str);
1397-
ExpandEnvironmentStrgs(hargs_str, false);
1397+
ExpandEnvironmentStrgs(hargs_str, false); // may realloc — args_buf is stale
13981398

1399-
ExtractFirstArgument(args_buf, file_buf, args2_buf, CMDLN_LENGTH_LIMIT);
1399+
ExtractFirstArgument(StrgGet(hargs_str), file_buf, args2_buf, CMDLN_LENGTH_LIMIT);
14001400
Path_Sanitize(hfile_pth);
14011401
StrgSanitize(hargs2_str);
14021402

@@ -1449,7 +1449,7 @@ static INT_PTR CALLBACK RunDlgProc(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM l
14491449

14501450
case IDOK: {
14511451
HPATHL hfile_pth = Path_Allocate(NULL);
1452-
wchar_t* const file_buf = Path_WriteAccessBuf(hfile_pth, CMDLN_LENGTH_LIMIT);
1452+
wchar_t* file_buf = Path_WriteAccessBuf(hfile_pth, CMDLN_LENGTH_LIMIT);
14531453
HSTRINGW hargs_str = StrgCreate(NULL);
14541454
wchar_t* const args_buf = StrgWriteAccessBuf(hargs_str, CMDLN_LENGTH_LIMIT);
14551455

@@ -1459,7 +1459,8 @@ static INT_PTR CALLBACK RunDlgProc(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM l
14591459

14601460
bool bQuickExit = false;
14611461

1462-
Path_ExpandEnvStrings(hfile_pth);
1462+
Path_ExpandEnvStrings(hfile_pth); // may realloc
1463+
file_buf = Path_WriteAccessBuf(hfile_pth, 0); // re-obtain after potential realloc
14631464
ExtractFirstArgument(file_buf, file_buf, args_buf, (int)Path_GetBufCount(hfile_pth));
14641465
Path_Sanitize(hfile_pth);
14651466
StrgSanitize(hargs_str);
@@ -4968,24 +4969,21 @@ typedef struct _grepwin_ini {
49684969
grepWin_t;
49694970

49704971
static grepWin_t grepWinIniSettings[] = {
4971-
{ L"onlyone", L"1" },
49724972
{ L"AllSize", L"0" },
4973-
{ L"Size", L"2000" },
4973+
{ L"CheckForUpdates", L"0" },
49744974
{ L"CreateBackup", L"1" },
49754975
{ L"DateLimit", L"0" },
49764976
{ L"IncludeBinary", L"0" },
49774977
{ L"IncludeHidden", L"1" },
49784978
{ L"IncludeSubfolders", L"1" },
49794979
{ L"IncludeSystem", L"1" },
4980+
{ L"onlyone", L"1" },
4981+
{ L"showcontent", L"1" },
4982+
{ L"Size", L"2000" },
49804983
{ L"UseFileMatchRegex", L"0" },
49814984
{ L"UTF8", L"1" }
49824985
};
49834986

4984-
//=============================================================================
4985-
//
4986-
// DialogGrepWin()
4987-
//
4988-
//
49894987
void DialogGrepWin(HWND hwnd, LPCWSTR searchPattern)
49904988
{
49914989
HPATHL hExeFilePath = Path_Allocate(Path_Get(Settings2.GrepWinPath));
@@ -5023,32 +5021,55 @@ void DialogGrepWin(HWND hwnd, LPCWSTR searchPattern)
50235021
HPATHL hGrepWinDir = Path_Allocate(Path_Get(hExeFilePath));
50245022
Path_RemoveFileSpec(hGrepWinDir);
50255023

5026-
HPATHL hGrepWinIniPath = Path_Copy(hExeFilePath); // side-by-side
5024+
// detect PortableApps installation (grepWinPortable.exe launcher)
5025+
LPCWSTR const wchExeFileName = Path_FindFileName(hExeFilePath);
5026+
bool const bIsPortableApps = (wchExeFileName && *wchExeFileName)
5027+
&& (CompareStringOrdinal(wchExeFileName, -1, L"grepWinPortable.exe", -1, TRUE) == CSTR_EQUAL);
5028+
5029+
5030+
if (!Path_IsExistingFile(hExeFilePath)) {
5031+
5032+
InfoBoxLng(MB_ICONWARNING, NULL, IDS_MUI_ERR_GREPWIN);
50275033

5028-
const WCHAR* const commandsSection = L"commands";
5034+
}
5035+
else {
50295036

5030-
if (Path_IsExistingFile(hExeFilePath)) {
5037+
HPATHL hGrepWinIniPath = Path_Copy(hExeFilePath); // side-by-side
50315038

50325039
// path to grepWin INI-File
50335040
Path_RemoveFileSpec(hGrepWinIniPath);
50345041

50355042
LPCWSTR const wchIniFileName = L"grepwin.ini";
5043+
if (bIsPortableApps) {
5044+
// PortableApps layout: <app-root>\Data\settings\grepwin.ini
5045+
Path_Append(hGrepWinIniPath, L"Data");
5046+
Path_Append(hGrepWinIniPath, L"settings");
5047+
}
50365048
Path_Append(hGrepWinIniPath, wchIniFileName);
50375049
if (Path_IsRelative(hGrepWinIniPath)) {
50385050
Path_Reset(hGrepWinIniPath, Path_Get(hGrepWinDir));
5051+
if (bIsPortableApps) {
5052+
Path_Append(hGrepWinIniPath, L"Data");
5053+
Path_Append(hGrepWinIniPath, L"settings");
5054+
}
50395055
Path_Append(hGrepWinIniPath, wchIniFileName);
50405056
}
50415057

5058+
5059+
// INI-File sections for cmdln settings
5060+
const WCHAR* const np3cmdSection = L"np3cmds";
5061+
50425062
// create/modify grepWin INI-File
50435063
ResetIniFileCache();
5064+
50445065
if (CreateIniFile(hGrepWinIniPath, NULL) && LoadIniFileCache(hGrepWinIniPath)) {
50455066

50465067
// =================================================================
50475068
// preserve [global] user settings from last call
50485069
// =================================================================
50495070
const WCHAR* const globalSection = L"global";
50505071

5051-
WCHAR value[LARGE_BUFFER];
5072+
WCHAR value[SMALL_BUFFER];
50525073
for (int i = 0; i < COUNTOF(grepWinIniSettings); ++i) {
50535074
IniSectionGetString(globalSection, grepWinIniSettings[i].key, grepWinIniSettings[i].val, value, COUNTOF(value));
50545075
IniSectionSetString(globalSection, grepWinIniSettings[i].key, value);
@@ -5063,8 +5084,8 @@ void DialogGrepWin(HWND hwnd, LPCWSTR searchPattern)
50635084
}
50645085
}
50655086

5066-
HPATHL hLngFilePath = Path_Allocate(NULL);
5067-
LPWSTR wchLngPathBuf = Path_WriteAccessBuf(hLngFilePath, PATHLONG_MAX_CCH);
5087+
HPATHL hLngFilePath = Path_Allocate(NULL);
5088+
LPWSTR wchLngPathBuf = Path_WriteAccessBuf(hLngFilePath, PATHLONG_MAX_CCH);
50685089
const WCHAR* const langFile = L"languagefile";
50695090

50705091
if (lngIdx >= 0) {
@@ -5074,9 +5095,15 @@ void DialogGrepWin(HWND hwnd, LPCWSTR searchPattern)
50745095
IniSectionDelete(globalSection, langFile, false);
50755096
}
50765097
else {
5077-
Path_CanonicalizeEx(hLngFilePath, hGrepWinDir);
5078-
wchLngPathBuf = Path_WriteAccessBuf(hLngFilePath, PATHLONG_MAX_CCH);
5079-
IniSectionSetString(globalSection, langFile, wchLngPathBuf);
5098+
// PortableApps: language files are in App\GrepWin\, not beside the launcher
5099+
HPATHL hLngBaseDir = Path_Copy(hGrepWinDir);
5100+
if (bIsPortableApps) {
5101+
Path_Append(hLngBaseDir, L"App");
5102+
Path_Append(hLngBaseDir, L"GrepWin");
5103+
}
5104+
Path_CanonicalizeEx(hLngFilePath, hLngBaseDir);
5105+
Path_Release(hLngBaseDir);
5106+
IniSectionSetString(globalSection, langFile, Path_Get(hLngFilePath));
50805107
}
50815108
}
50825109
else {
@@ -5108,6 +5135,37 @@ void DialogGrepWin(HWND hwnd, LPCWSTR searchPattern)
51085135
Path_Release(hpath_np3);
51095136
Path_Release(hLngFilePath);
51105137

5138+
// search directory
5139+
HPATHL pthSearchDir = NULL;
5140+
if (Path_IsNotEmpty(Paths.CurrentFile)) {
5141+
pthSearchDir = Path_Copy(Paths.CurrentFile);
5142+
Path_RemoveFileSpec(pthSearchDir);
5143+
}
5144+
else {
5145+
pthSearchDir = Path_Copy(Paths.WorkingDirectory);
5146+
}
5147+
IniSectionSetString(globalSection, L"searchpath", Path_Get(pthSearchDir));
5148+
Path_Release(pthSearchDir);
5149+
5150+
5151+
// =================================================================
5152+
// [np3cmds]
5153+
// =================================================================
5154+
5155+
// search pattern
5156+
if (StrIsNotEmpty(searchPattern))
5157+
IniSectionSetString(np3cmdSection, L"searchfor", searchPattern);
5158+
else {
5159+
IniSectionSetString(np3cmdSection, L"searchfor", StrgGet(Settings.EFR_Data.chFindPattern));
5160+
IniSectionSetString(np3cmdSection, L"replacewith", StrgGet(Settings.EFR_Data.chReplaceTemplate));
5161+
}
5162+
5163+
StrgCat(hstrOptions, ((Settings.EFR_Data.fuFlags & SCFIND_MATCHCASE) != 0) ? L" /i:no " : L" /i:yes");
5164+
StrgCat(hstrOptions, ((Settings.EFR_Data.fuFlags & SCFIND_DOT_MATCH_ALL) != 0) ? L" /n:yes" : L" /n:no");
5165+
StrgCat(hstrOptions, ((Settings.EFR_Data.fuFlags & SCFIND_WHOLEWORD) != 0) ? L" /wholewords:yes" : L" /wholewords:no");
5166+
StrgCat(hstrOptions, Settings.EFR_Data.bRegExprSearch ? L" /regex:yes" : L" /regex:no");
5167+
5168+
51115169
// =================================================================
51125170
// [settings]
51135171
// =================================================================
@@ -5118,64 +5176,41 @@ void DialogGrepWin(HWND hwnd, LPCWSTR searchPattern)
51185176
int const iBackupInFolder = IniSectionGetInt(settingsSection, L"backupinfolder", 1);
51195177
IniSectionSetInt(settingsSection, L"backupinfolder", iBackupInFolder);
51205178

5179+
51215180
// =================================================================
51225181
// [export]
51235182
// =================================================================
51245183
const WCHAR* const exportSection = L"export";
5125-
int const iExpPaths = IniSectionGetInt(exportSection, L"paths", 1);
5184+
int const iExpPaths = IniSectionGetInt(exportSection, L"paths", 1);
51265185
IniSectionSetInt(exportSection, L"paths", iExpPaths);
51275186
int const iExpLnNums = IniSectionGetInt(exportSection, L"linenumbers", 1);
51285187
IniSectionSetInt(exportSection, L"linenumbers", iExpLnNums);
51295188
int const iExpContent = IniSectionGetInt(exportSection, L"linecontent", 1);
51305189
IniSectionSetInt(exportSection, L"linecontent", iExpContent);
51315190

5191+
SaveIniFileCache(hGrepWinIniPath);
5192+
ResetIniFileCache();
5193+
}
51325194

5133-
// =================================================================
5134-
// [commands]
5135-
// =================================================================
5136-
// see above: const WCHAR* const commandsSection = L"commands";
5137-
5138-
// search directory
5139-
HPATHL pthSearchDir = NULL;
5140-
if (Path_IsNotEmpty(Paths.CurrentFile)) {
5141-
pthSearchDir = Path_Copy(Paths.CurrentFile);
5142-
Path_RemoveFileSpec(pthSearchDir);
5195+
// grepWin arguments (omit /portable for PortableApps — the launcher sets it)
5196+
HSTRINGW hstrParams = StrgCreate(L"");
5197+
HSTRINGW hstrEscPattern = EscapeStringForCmdLine(searchPattern);
5198+
if (Path_IsExistingFile(hGrepWinIniPath)) {
5199+
if (bIsPortableApps) {
5200+
StrgFormat(hstrParams, L"/content %s /searchfor:\"%s\"", StrgGet(hstrOptions), StrgGet(hstrEscPattern));
51435201
}
51445202
else {
5145-
pthSearchDir = Path_Copy(Paths.WorkingDirectory);
5203+
StrgFormat(hstrParams, L"/portable /content %s /searchini:\"%s\" /name:\"%s\"",
5204+
StrgGet(hstrOptions), Path_Get(hGrepWinIniPath), np3cmdSection);
51465205
}
5147-
IniSectionSetString(commandsSection, L"searchpath", Path_Get(pthSearchDir));
5148-
Path_Release(pthSearchDir);
5149-
5150-
// search pattern
5151-
if (StrIsNotEmpty(searchPattern))
5152-
IniSectionSetString(commandsSection, L"searchfor", searchPattern);
5153-
else {
5154-
IniSectionSetString(commandsSection, L"searchfor", StrgGet(Settings.EFR_Data.chFindPattern));
5155-
IniSectionSetString(commandsSection, L"replacewith", StrgGet(Settings.EFR_Data.chReplaceTemplate));
5156-
}
5157-
5158-
int const iRegex = IniSectionGetInt(commandsSection, L"regex", Settings.EFR_Data.bRegExprSearch ? 1 : 0);
5159-
IniSectionSetInt(commandsSection, L"regex", iRegex);
5160-
5161-
SaveIniFileCache(hGrepWinIniPath);
5162-
ResetIniFileCache();
51635206
}
5164-
}
5165-
5166-
// grepWin arguments
5167-
HSTRINGW hstrParams = StrgCreate(L"");
5168-
if (Path_IsExistingFile(hGrepWinIniPath)) {
5169-
StrgFormat(hstrParams, L"/portable /content %s /searchini:\"%s\" /name:\"%s\"",
5170-
StrgGet(hstrOptions), Path_Get(hGrepWinIniPath), commandsSection);
5171-
} else {
5172-
StrgFormat(hstrParams, L"/portable /content %s", StrgGet(hstrOptions));
5173-
}
5207+
else {
5208+
StrgFormat(hstrParams, bIsPortableApps ?
5209+
L"/content %s /searchfor:\"%s\"" : L"/portable /content %s /searchfor:\"%s\"",
5210+
StrgGet(hstrOptions), StrgGet(hstrEscPattern));
5211+
}
5212+
StrgDestroy(hstrEscPattern);
51745213

5175-
if (!Path_IsExistingFile(hExeFilePath)) {
5176-
InfoBoxLng(MB_ICONWARNING, NULL, IDS_MUI_ERR_GREPWIN);
5177-
}
5178-
else {
51795214
SHELLEXECUTEINFO sei = { sizeof(SHELLEXECUTEINFO) };
51805215
sei.fMask = SEE_MASK_FLAG_NO_UI | SEE_MASK_NOZONECHECKS;
51815216
sei.hwnd = hwnd;
@@ -5187,14 +5222,13 @@ void DialogGrepWin(HWND hwnd, LPCWSTR searchPattern)
51875222
if (!ShellExecuteExW(&sei) || (INT_PTR)sei.hInstApp < 32) {
51885223
InfoBoxLng(MB_ICONWARNING, NULL, IDS_MUI_ERR_GREPWIN);
51895224
}
5225+
StrgDestroy(hstrParams);
5226+
Path_Release(hGrepWinIniPath);
51905227
}
51915228

51925229
StrgDestroy(hstrOptions);
5193-
StrgDestroy(hstrParams);
5194-
5195-
Path_Release(hGrepWinIniPath);
5196-
Path_Release(hGrepWinDir);
51975230
Path_Release(hExeFilePath);
5231+
Path_Release(hGrepWinDir);
51985232
}
51995233

52005234

0 commit comments

Comments
 (0)