libexpr: Warn on incorrect string escapes

In Nixpkgs, there are several strings like "\d\.\d" which attempt to be
a regex but are just literally "d.d". The escaping rules are silly and
we should warn our users about that.

Co-authored-by: Commentator2.0 <lix@crystal-cavern.systems>
Change-Id: I779b0757358fc9adc34dc140e1670b83abc93b67
This commit is contained in:
piegames
2026-01-31 15:32:27 +01:00
co-authored by Commentator2.0
parent f1fbd1d095
commit 56dee9186f
18 changed files with 158 additions and 9 deletions
+29 -5
View File
@@ -580,14 +580,17 @@ struct StringState : SubexprState {
if (c == 'n') *t = '\n';
else if (c == 'r') *t = '\r';
else if (c == 't') *t = '\t';
else *t = c;
else {
*t = c;
}
}
else if (c == '\r') {
/* Normalise CR and CR/LF into LF. */
*t = '\n';
if (*s == '\n') s++; /* cr/lf */
} else {
*t = c;
}
else *t = c;
t++;
}
if (!ps.featureSettings.isEnabled(Dep::NulBytes) && size_t(s - str.data() - 1) != str.size())
@@ -640,8 +643,15 @@ template<> struct BuildAST<grammar::v1::string::interpolation> {
template<> struct BuildAST<grammar::v1::string::escape> {
static void apply(const auto & in, StringState & s, State & ps) {
if (!ps.featureSettings.isEnabled(Dep::NulBytes) && *in.begin() == '\0')
char c = *in.begin();
if (!ps.featureSettings.isEnabled(Dep::NulBytes) && c == '\0') {
ps.nulFound(ps.at(in));
}
if (!ps.featureSettings.isEnabled(Dep::BrokenStringEscape) && c != '\\' && c != '$'
&& c != '"' && c != 'r' && c != 'n' && c != 't')
{
ps.badEscapeFound(ps.at(in), c, "\\");
}
s.append(ps.at(in), "\\"); // FIXME compat with old parser
s.append(ps.at(in), in.string_view());
}
@@ -691,17 +701,31 @@ template<> struct BuildAST<grammar::v1::ind_string::interpolation> {
template<> struct BuildAST<grammar::v1::ind_string::escape> {
static void apply(const auto & in, IndStringState & s, State & ps) {
switch (*in.begin()) {
auto c = *in.begin();
switch (c) {
case 'n': s.lines.back().parts.emplace_back(ps.at(in), "\n"); break;
case 'r': s.lines.back().parts.emplace_back(ps.at(in), "\r"); break;
case 't': s.lines.back().parts.emplace_back(ps.at(in), "\t"); break;
// TODO merge with below
// `''\'` must escape to itself even though one can just write `'` instead, because of
// shit like
// `''\'''${` to express the string `'${` (remember that `'''` escapes to `''`)
case '\'':
s.lines.back().parts.emplace_back(ps.at(in), "'");
break;
case 0:
if (!ps.featureSettings.isEnabled(Dep::NulBytes)) {
ps.nulFound(ps.at(in));
break;
}
KJ_FALLTHROUGH;
default: s.lines.back().parts.emplace_back(ps.at(in), in.string_view()); break;
default:
if (!ps.featureSettings.isEnabled(Dep::BrokenStringEscape)) {
ps.badEscapeFound(ps.at(in), c, "''\\");
}
s.lines.back().parts.emplace_back(ps.at(in), in.string_view());
break;
}
}
};