From c93f0581c6b7794d8c1d5637c5c4dabd591b1d57 Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Sun, 25 Nov 2018 01:50:35 +0000 Subject: [PATCH] Disallow parent selectors in `selector-append` (#2760) Now correctly errors on inputs such as this one instead of crashing (see #2663): ```scss div { a: selector-append('.menu', 'li', '&'); } ``` ``` Error: Parent selectors aren't allowed here. on line 3:37 of ../../../tmp/test.scss, in function `selector-append` from line 3:6 of ../../../tmp/test.scss >> e: selector-append('.menu', 'li', '&'); ------------------------------------^ ``` --- src/fn_selectors.cpp | 5 ++++- src/fn_utils.cpp | 8 ++++---- src/parser.cpp | 14 +++++++------- src/parser.hpp | 13 +++++++------ 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/fn_selectors.cpp b/src/fn_selectors.cpp index aa92ace75..e2e22e9e5 100644 --- a/src/fn_selectors.cpp +++ b/src/fn_selectors.cpp @@ -83,7 +83,10 @@ namespace Sass { str->quote_mark(0); } std::string exp_src = exp->to_string(); - Selector_List_Obj sel = Parser::parse_selector(exp_src.c_str(), ctx, traces); + Selector_List_Obj sel = Parser::parse_selector(exp_src.c_str(), ctx, traces, + exp->pstate(), pstate.src, + /*allow_parent=*/false); + parsedSelectors.push_back(sel); } diff --git a/src/fn_utils.cpp b/src/fn_utils.cpp index a90f52c49..45100b221 100644 --- a/src/fn_utils.cpp +++ b/src/fn_utils.cpp @@ -121,13 +121,13 @@ namespace Sass { std::stringstream msg; msg << argname << ": null is not a valid selector: it must be a string,\n"; msg << "a list of strings, or a list of lists of strings for `" << function_name(sig) << "'"; - error(msg.str(), pstate, traces); + error(msg.str(), exp->pstate(), traces); } if (String_Constant_Ptr str = Cast(exp)) { str->quote_mark(0); } std::string exp_src = exp->to_string(ctx.c_options); - return Parser::parse_selector(exp_src.c_str(), ctx, traces); + return Parser::parse_selector(exp_src.c_str(), ctx, traces, exp->pstate(), pstate.src); } Compound_Selector_Obj get_arg_sel(const std::string& argname, Env& env, Signature sig, ParserState pstate, Backtraces traces, Context& ctx) { @@ -135,13 +135,13 @@ namespace Sass { if (exp->concrete_type() == Expression::NULL_VAL) { std::stringstream msg; msg << argname << ": null is not a string for `" << function_name(sig) << "'"; - error(msg.str(), pstate, traces); + error(msg.str(), exp->pstate(), traces); } if (String_Constant_Ptr str = Cast(exp)) { str->quote_mark(0); } std::string exp_src = exp->to_string(ctx.c_options); - Selector_List_Obj sel_list = Parser::parse_selector(exp_src.c_str(), ctx, traces); + Selector_List_Obj sel_list = Parser::parse_selector(exp_src.c_str(), ctx, traces, exp->pstate(), pstate.src); if (sel_list->length() == 0) return {}; Complex_Selector_Obj first = sel_list->first(); if (!first->tail()) return first->head(); diff --git a/src/parser.cpp b/src/parser.cpp index 26fb2c95e..374ffa138 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -30,11 +30,11 @@ namespace Sass { using namespace Constants; using namespace Prelexer; - Parser Parser::from_c_str(const char* beg, Context& ctx, Backtraces traces, ParserState pstate, const char* source) + Parser Parser::from_c_str(const char* beg, Context& ctx, Backtraces traces, ParserState pstate, const char* source, bool allow_parent) { pstate.offset.column = 0; pstate.offset.line = 0; - Parser p(ctx, pstate, traces); + Parser p(ctx, pstate, traces, allow_parent); p.source = source ? source : beg; p.position = beg ? beg : p.source; p.end = p.position + strlen(p.position); @@ -44,11 +44,11 @@ namespace Sass { return p; } - Parser Parser::from_c_str(const char* beg, const char* end, Context& ctx, Backtraces traces, ParserState pstate, const char* source) + Parser Parser::from_c_str(const char* beg, const char* end, Context& ctx, Backtraces traces, ParserState pstate, const char* source, bool allow_parent) { pstate.offset.column = 0; pstate.offset.line = 0; - Parser p(ctx, pstate, traces); + Parser p(ctx, pstate, traces, allow_parent); p.source = source ? source : beg; p.position = beg ? beg : p.source; p.end = end ? end : p.position + strlen(p.position); @@ -66,10 +66,9 @@ namespace Sass { pstate.offset.line = 0; } - Selector_List_Obj Parser::parse_selector(const char* beg, Context& ctx, Backtraces traces, ParserState pstate, const char* source) + Selector_List_Obj Parser::parse_selector(const char* beg, Context& ctx, Backtraces traces, ParserState pstate, const char* source, bool allow_parent) { - Parser p = Parser::from_c_str(beg, ctx, traces, pstate, source); - // ToDo: ruby sass errors on parent references + Parser p = Parser::from_c_str(beg, ctx, traces, pstate, source, allow_parent); // ToDo: remap the source-map entries somehow return p.parse_selector_list(false); } @@ -818,6 +817,7 @@ namespace Sass { // parse parent selector else if (lex< exactly<'&'> >(false)) { + if (!allow_parent) error("Parent selectors aren't allowed here."); // this produces a linefeed!? seq->has_parent_reference(true); seq->append(SASS_MEMORY_NEW(Parent_Selector, pstate)); diff --git a/src/parser.hpp b/src/parser.hpp index 7a88e2451..7ee515b75 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -48,23 +48,24 @@ namespace Sass { Backtraces traces; size_t indentation; size_t nestings; + bool allow_parent; Token lexed; - Parser(Context& ctx, const ParserState& pstate, Backtraces traces) + Parser(Context& ctx, const ParserState& pstate, Backtraces traces, bool allow_parent = true) : ParserState(pstate), ctx(ctx), block_stack(), stack(0), last_media_block(), source(0), position(0), end(0), before_token(pstate), after_token(pstate), - pstate(pstate), traces(traces), indentation(0), nestings(0) + pstate(pstate), traces(traces), indentation(0), nestings(0), allow_parent(allow_parent) { stack.push_back(Scope::Root); } // static Parser from_string(const std::string& src, Context& ctx, ParserState pstate = ParserState("[STRING]")); - static Parser from_c_str(const char* src, Context& ctx, Backtraces, ParserState pstate = ParserState("[CSTRING]"), const char* source = 0); - static Parser from_c_str(const char* beg, const char* end, Context& ctx, Backtraces, ParserState pstate = ParserState("[CSTRING]"), const char* source = 0); - static Parser from_token(Token t, Context& ctx, Backtraces, ParserState pstate = ParserState("[TOKEN]"), const char* source = 0); + static Parser from_c_str(const char* src, Context& ctx, Backtraces, ParserState pstate = ParserState("[CSTRING]"), const char* source = nullptr, bool allow_parent = true); + static Parser from_c_str(const char* beg, const char* end, Context& ctx, Backtraces, ParserState pstate = ParserState("[CSTRING]"), const char* source = nullptr, bool allow_parent = true); + static Parser from_token(Token t, Context& ctx, Backtraces, ParserState pstate = ParserState("[TOKEN]"), const char* source = nullptr); // special static parsers to convert strings into certain selectors - static Selector_List_Obj parse_selector(const char* src, Context& ctx, Backtraces, ParserState pstate = ParserState("[SELECTOR]"), const char* source = 0); + static Selector_List_Obj parse_selector(const char* src, Context& ctx, Backtraces, ParserState pstate = ParserState("[SELECTOR]"), const char* source = nullptr, bool allow_parent = true); #ifdef __clang__