fix(rtl,pp): pre-release safety round (Wave 2)
Five concrete gaps the audit flagged in the new __dbCopy / __dbSort /
__dbTotal / __dbJoin / PP code:
* wam.Close() errors were dropped on the floor. Caller saw `.T.`
even when the just-written DBF wasn't durable, leading to the
classic "delete the source after the COPY succeeds" data-loss
pattern. All four functions now capture the close error and
return `.F.` if it fired.
* drv.Create succeeded → wam.Open failed → orphaned-on-disk DBF.
The user-named target file was left around with zero records,
and the next call's drv.Create silently truncated it instead of
surfacing the original error. Add `os.Remove(cFile)` on the
Open-failure cleanup path for COPY/SORT/TOTAL/JOIN.
* __dbTotal would write the DBF codec's overflow sentinel
(`*****`) into the destination's sum-fields when a group total
didn't fit in the source's declared field width, and still
return `.T.`. Now: precompute each sum-field's max representable
magnitude (10^(Len-Dec)) at start, mark the run as overflowed if
any flush sees an out-of-range or NaN value, and propagate
`.F.` to the caller so they don't trust the file.
* cleanUnreferencedMarkers walked byte-by-byte and stripped any
`<ident>` token in the result, INCLUDING ones that appear
inside `"..."` / `'...'` string literals. A user expression
like `LIST FOR url == "<a>x</a>"` got the `<a>` and `</a>`
eaten on output. Now: track string-literal state and skip the
cleanup pass while inside one. Bracket-strings `[…]` are
intentionally not treated as strings here — the result template
uses `[...]` as the optional-repeat marker, and disambiguating
needs context the cleanup pass doesn't have.
* (#8 SET SAFETY honoring) deferred. Harbour default is SAFETY
OFF, so the current always-overwrite behavior matches default
Harbour. The divergence only matters when user explicitly does
`SET SAFETY ON`, which Five doesn't support yet — so the
no-overwrite-protection is consistent end-to-end. Tracked as a
separate followup.
Gates green:
go test ./... : PASS
FiveSql2 SQL:1999 : 43/43
Harbour compat : 56/56
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1059,30 +1059,54 @@ func referencedMarkers(s string) []string {
|
||||
return out
|
||||
}
|
||||
|
||||
// cleanUnreferencedMarkers removes any remaining <name>, <(name)>, <.name.>, #<name> references.
|
||||
// Only removes well-formed PP marker references, not comparison operators.
|
||||
// cleanUnreferencedMarkers removes any remaining <name>, <(name)>,
|
||||
// <.name.>, #<name> references. Only removes well-formed PP marker
|
||||
// references, not comparison operators. Skips over PRG string
|
||||
// literals ("...", '...', [...]) so a captured value containing
|
||||
// `<a>` text (e.g. "<a>http://x</a>" inside a regex/string) isn't
|
||||
// gutted — that pass used to corrupt arbitrary string content.
|
||||
func cleanUnreferencedMarkers(s string) string {
|
||||
// Match patterns like <identifier>, <(identifier)>, <.identifier.>, #<identifier>
|
||||
var out strings.Builder
|
||||
i := 0
|
||||
inStr := byte(0)
|
||||
for i < len(s) {
|
||||
c := s[i]
|
||||
// Inside a string literal: copy until the matching closer.
|
||||
// Bracket-strings `[...]` are PRG-specific but are also used
|
||||
// as the result template's optional-repeat brackets, so we
|
||||
// leave them out of this pass — only `'…'` and `"…"` are
|
||||
// unambiguous strings here.
|
||||
if inStr != 0 {
|
||||
out.WriteByte(c)
|
||||
if c == inStr {
|
||||
inStr = 0
|
||||
}
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if c == '"' || c == '\'' {
|
||||
inStr = c
|
||||
out.WriteByte(c)
|
||||
i++
|
||||
continue
|
||||
}
|
||||
removed := false
|
||||
// #<name>
|
||||
if s[i] == '#' && i+1 < len(s) && s[i+1] == '<' {
|
||||
if c == '#' && i+1 < len(s) && s[i+1] == '<' {
|
||||
if end := findMarkerEnd(s, i+1); end > 0 {
|
||||
i = end
|
||||
removed = true
|
||||
}
|
||||
}
|
||||
// <name>, <(name)>, <.name.>, <"name">
|
||||
if !removed && s[i] == '<' {
|
||||
if !removed && c == '<' {
|
||||
if end := findMarkerEnd(s, i); end > 0 {
|
||||
i = end
|
||||
removed = true
|
||||
}
|
||||
}
|
||||
if !removed {
|
||||
out.WriteByte(s[i])
|
||||
out.WriteByte(c)
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user