Skip to content

Commit b597681

Browse files
committed
Merge pull request #1527 from mgreter/bugfix/parent-selector-segfaults
Fix two segfaults with parent selectors
2 parents 9b07cbd + 89adfaa commit b597681

File tree

8 files changed

+76
-32
lines changed

8 files changed

+76
-32
lines changed

src/ast.cpp

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1125,13 +1125,18 @@ namespace Sass {
11251125
// Check every rhs selector against left hand list
11261126
for(size_t i = 0, L = length(); i < L; ++i) {
11271127
if (!(*this)[i]->head()) continue;
1128-
if ((*this)[i]->combinator() != Complex_Selector::ANCESTOR_OF) continue;
11291128
if ((*this)[i]->head()->is_empty_reference()) {
1130-
Complex_Selector* tail = (*this)[i]->tail();
1131-
// if ((*this)[i]->has_line_feed()) {
1132-
// if (tail) tail->has_line_feed(true);
1133-
// }
1134-
(*this)[i] = tail;
1129+
// simply move to the next tail if we have "no" combinator
1130+
if ((*this)[i]->combinator() == Complex_Selector::ANCESTOR_OF) {
1131+
if ((*this)[i]->tail() && (*this)[i]->has_line_feed()) {
1132+
(*this)[i]->tail()->has_line_feed(true);
1133+
}
1134+
(*this)[i] = (*this)[i]->tail();
1135+
}
1136+
// otherwise remove the first item from head
1137+
else {
1138+
(*this)[i]->head()->erase((*this)[i]->head()->begin());
1139+
}
11351140
}
11361141
}
11371142
}

src/ast.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,9 +405,10 @@ namespace Sass {
405405
class Ruleset : public Has_Block {
406406
ADD_PROPERTY(Selector*, selector)
407407
ADD_PROPERTY(bool, at_root);
408+
ADD_PROPERTY(bool, is_root);
408409
public:
409410
Ruleset(ParserState pstate, Selector* s = 0, Block* b = 0)
410-
: Has_Block(pstate, b), selector_(s), at_root_(false)
411+
: Has_Block(pstate, b), selector_(s), at_root_(false), is_root_(false)
411412
{ statement_type(RULESET); }
412413
bool is_invisible() const;
413414
// nested rulesets need to be hoisted out of their enclosing blocks

src/debugger.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,7 @@ inline void debug_ast(AST_Node* node, std::string ind, Env* env)
394394
std::cerr << " (" << pstate_source_position(node) << ")";
395395
std::cerr << " [indent: " << ruleset->tabs() << "]";
396396
std::cerr << (ruleset->at_root() ? " [@ROOT]" : "");
397+
std::cerr << (ruleset->is_root() ? " [root]" : "");
397398
std::cerr << std::endl;
398399
debug_ast(ruleset->selector(), ind + ">");
399400
debug_ast(ruleset->block(), ind + " ");

src/expand.cpp

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,23 @@ namespace Sass {
9797
return k;
9898
}
9999

100+
// do some special checks for the base level rules
101+
if (r->is_root()) {
102+
if (Selector_List* selector_list = dynamic_cast<Selector_List*>(r->selector())) {
103+
for (Complex_Selector* complex_selector : selector_list->elements()) {
104+
Complex_Selector* tail = complex_selector;
105+
while (tail) {
106+
if (tail->head()) for (Simple_Selector* header : tail->head()->elements()) {
107+
if (dynamic_cast<Parent_Selector*>(header) == NULL) continue; // skip all others
108+
To_String to_string(&ctx); std::string sel_str(complex_selector->perform(&to_string));
109+
error("Base-level rules cannot contain the parent-selector-referencing character '&'.", header->pstate(), backtrace());
110+
}
111+
tail = tail->tail();
112+
}
113+
}
114+
}
115+
}
116+
100117
Expression* ex = r->selector()->perform(&eval);
101118
Selector_List* sel = dynamic_cast<Selector_List*>(ex);
102119
if (sel == 0) throw std::runtime_error("Expanded null selector");
@@ -499,21 +516,34 @@ namespace Sass {
499516
Statement* Expand::operator()(Extension* e)
500517
{
501518
To_String to_string(&ctx);
502-
Selector_List* extender = static_cast<Selector_List*>(selector());
519+
Selector_List* extender = dynamic_cast<Selector_List*>(selector());
503520
if (!extender) return 0;
504521
selector_stack.push_back(0);
505-
// extender->remove_parent_selectors();
506522

507-
Selector_List* selector_list = static_cast<Selector_List*>(e->selector());
508-
Selector_List* contextualized = static_cast<Selector_List*>(selector_list->perform(&eval));
509-
// contextualized->remove_parent_selectors();
523+
if (Selector_List* selector_list = dynamic_cast<Selector_List*>(e->selector())) {
524+
for (Complex_Selector* complex_selector : selector_list->elements()) {
525+
Complex_Selector* tail = complex_selector;
526+
while (tail) {
527+
if (tail->head()) for (Simple_Selector* header : tail->head()->elements()) {
528+
if (dynamic_cast<Parent_Selector*>(header) == NULL) continue; // skip all others
529+
To_String to_string(&ctx); std::string sel_str(complex_selector->perform(&to_string));
530+
error("Can't extend " + sel_str + ": can't extend parent selectors", header->pstate(), backtrace());
531+
}
532+
tail = tail->tail();
533+
}
534+
}
535+
}
536+
537+
Selector_List* contextualized = dynamic_cast<Selector_List*>(e->selector()->perform(&eval));
538+
if (contextualized == NULL) return 0;
510539
for (auto complex_sel : contextualized->elements()) {
511540
Complex_Selector* c = complex_sel;
512541
if (!c->head() || c->tail()) {
513-
error("nested selectors may not be extended", c->pstate(), backtrace());
542+
To_String to_string(&ctx); std::string sel_str(contextualized->perform(&to_string));
543+
error("Can't extend " + sel_str + ": can't extend nested selectors", c->pstate(), backtrace());
514544
}
515545
Compound_Selector* placeholder = c->head();
516-
placeholder->is_optional(selector_list->is_optional());
546+
placeholder->is_optional(e->selector()->is_optional());
517547
for (size_t i = 0, L = extender->length(); i < L; ++i) {
518548
Complex_Selector* sel = (*extender)[i];
519549
if (!(sel->head() && sel->head()->length() > 0 &&
@@ -531,6 +561,7 @@ namespace Sass {
531561
ctx.subset_map.put(placeholder->to_str_vec(), std::make_pair(sel, placeholder));
532562
}
533563
}
564+
534565
selector_stack.pop_back();
535566

536567
return 0;

src/extend.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2023,6 +2023,7 @@ namespace Sass {
20232023
if (extendedSomething && pNewSelectorList) {
20242024
DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND ORIGINAL SELECTORS: " << static_cast<Selector_List*>(pObject->selector())->perform(&to_string))
20252025
DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND SETTING NEW SELECTORS: " << pNewSelectorList->perform(&to_string))
2026+
pNewSelectorList->remove_parent_selectors();
20262027
pObject->selector(pNewSelectorList);
20272028
} else {
20282029
DEBUG_PRINTLN(EXTEND_OBJECT, "EXTEND DID NOT TRY TO EXTEND ANYTHING")

src/inspect.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -768,7 +768,7 @@ namespace Sass {
768768
}
769769
}
770770

771-
if (head && !head->is_empty_reference()) head->perform(this);
771+
if (head && head->length() != 0) head->perform(this);
772772
bool is_empty = !head || head->length() == 0 || head->is_empty_reference();
773773
bool is_tail = head && !head->is_empty_reference() && tail;
774774
if (output_style() == COMPRESSED && comb != Complex_Selector::ANCESTOR_OF) scheduled_space = 0;

src/parser.cpp

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ namespace Sass {
4848
Parser p = Parser::from_c_str(src, ctx, pstate);
4949
// ToDo: ruby sass errors on parent references
5050
// ToDo: remap the source-map entries somehow
51-
return p.parse_selector_list();
51+
return p.parse_selector_list(false);
5252
}
5353

5454
bool Parser::peek_newline(const char* start)
@@ -72,10 +72,12 @@ namespace Sass {
7272
/* main entry point to parse root block */
7373
Block* Parser::parse()
7474
{
75+
bool is_root = false;
7576
Block* root = SASS_MEMORY_NEW(ctx.mem, Block, pstate, 0, true);
7677
read_bom();
7778

7879
if (ctx.queue.size() == 1) {
80+
is_root = true;
7981
Import* pre = SASS_MEMORY_NEW(ctx.mem, Import, pstate);
8082
std::string load_path(ctx.queue[0].load_path);
8183
do_import(load_path, pre, ctx.c_headers, false);
@@ -89,7 +91,7 @@ namespace Sass {
8991
}
9092

9193
block_stack.push_back(root);
92-
/* bool rv = */ parse_block_nodes();
94+
/* bool rv = */ parse_block_nodes(is_root);
9395
block_stack.pop_back();
9496

9597
// update for end position
@@ -147,7 +149,7 @@ namespace Sass {
147149

148150
// the main block parsing function
149151
// parses stuff between `{` and `}`
150-
bool Parser::parse_block_nodes()
152+
bool Parser::parse_block_nodes(bool is_root)
151153
{
152154

153155
// loop until end of string
@@ -161,7 +163,7 @@ namespace Sass {
161163
if (peek < end_of_file >()) return true;
162164
if (peek < exactly<'}'> >()) return true;
163165

164-
if (parse_block_node()) continue;
166+
if (parse_block_node(is_root)) continue;
165167

166168
parse_block_comments();
167169

@@ -178,7 +180,7 @@ namespace Sass {
178180

179181
// parser for a single node in a block
180182
// semicolons must be lexed beforehand
181-
bool Parser::parse_block_node() {
183+
bool Parser::parse_block_node(bool is_root) {
182184

183185
Block* block = block_stack.back();
184186

@@ -237,13 +239,13 @@ namespace Sass {
237239
if (!lookahead.found) css_error("Invalid CSS", " after ", ": expected selector, was ");
238240
Selector* target;
239241
if (lookahead.has_interpolants) target = parse_selector_schema(lookahead.found);
240-
else target = parse_selector_list();
242+
else target = parse_selector_list(true);
241243
(*block) << SASS_MEMORY_NEW(ctx.mem, Extension, pstate, target);
242244
}
243245

244246
// selector may contain interpolations which need delayed evaluation
245247
else if (!(lookahead_result = lookahead_for_selector(position)).error)
246-
{ (*block) << parse_ruleset(lookahead_result); }
248+
{ (*block) << parse_ruleset(lookahead_result, is_root); }
247249

248250
// parse multiple specific keyword directives
249251
else if (lex < kwd_media >(true)) { (*block) << parse_media_block(); }
@@ -566,19 +568,22 @@ namespace Sass {
566568
}
567569

568570
// a ruleset connects a selector and a block
569-
Ruleset* Parser::parse_ruleset(Lookahead lookahead)
571+
Ruleset* Parser::parse_ruleset(Lookahead lookahead, bool is_root)
570572
{
571573
// make sure to move up the the last position
572574
lex < optional_css_whitespace >(false, true);
573575
// create the connector object (add parts later)
574576
Ruleset* ruleset = SASS_MEMORY_NEW(ctx.mem, Ruleset, pstate);
575577
// parse selector static or as schema to be evaluated later
576-
if (lookahead.parsable) ruleset->selector(parse_selector_list());
578+
if (lookahead.parsable) ruleset->selector(parse_selector_list(is_root));
577579
else ruleset->selector(parse_selector_schema(lookahead.found));
578580
// then parse the inner block
579581
ruleset->block(parse_block());
580582
// update for end position
581583
ruleset->update_pstate(pstate);
584+
// inherit is_root from parent block
585+
// need this info for sanity checks
586+
ruleset->is_root(is_root);
582587
// return AST Node
583588
return ruleset;
584589
}
@@ -768,7 +773,7 @@ namespace Sass {
768773
// check if we got the abort condition (ToDo: optimize)
769774
if (!peek_css< class_char < complex_selector_delims > >()) {
770775
// parse next selector in sequence
771-
sel->tail(parse_complex_selector());
776+
sel->tail(parse_complex_selector(true));
772777
if (sel->tail()) {
773778
// ToDo: move this logic below into tail setter
774779
if (sel->tail()->has_reference()) sel->has_reference(true);
@@ -889,7 +894,7 @@ namespace Sass {
889894
lex< pseudo_not >();
890895
std::string name(lexed);
891896
ParserState nsource_position = pstate;
892-
Selector* negated = parse_selector_list();
897+
Selector* negated = parse_selector_list(true);
893898
if (!lex< exactly<')'> >()) {
894899
error("negated selector is missing ')'", pstate);
895900
}
@@ -933,7 +938,7 @@ namespace Sass {
933938
return SASS_MEMORY_NEW(ctx.mem, Pseudo_Selector, p, name, expr);
934939
}
935940
}
936-
else if (Selector* wrapped = parse_selector_list()) {
941+
else if (Selector* wrapped = parse_selector_list(true)) {
937942
if (wrapped && lex_css< exactly<')'> >()) {
938943
return SASS_MEMORY_NEW(ctx.mem, Wrapped_Selector, p, name, wrapped);
939944
}
@@ -2066,7 +2071,7 @@ namespace Sass {
20662071
body = parse_block(true);
20672072
}
20682073
else if ((lookahead_result = lookahead_for_selector(position)).found) {
2069-
Ruleset* r = parse_ruleset(lookahead_result);
2074+
Ruleset* r = parse_ruleset(lookahead_result, false);
20702075
body = SASS_MEMORY_NEW(ctx.mem, Block, r->pstate(), 1, true);
20712076
*body << r;
20722077
}
@@ -2105,7 +2110,7 @@ namespace Sass {
21052110
At_Rule* at_rule = SASS_MEMORY_NEW(ctx.mem, At_Rule, pstate, kwd);
21062111
Lookahead lookahead = lookahead_for_include(position);
21072112
if (lookahead.found && !lookahead.has_interpolants) {
2108-
at_rule->selector(parse_selector_list());
2113+
at_rule->selector(parse_selector_list(true));
21092114
}
21102115

21112116
lex < css_comments >(false);

src/parser.hpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ namespace Sass {
223223
Argument* parse_argument(bool has_url = false);
224224
Assignment* parse_assignment();
225225
// Propset* parse_propset();
226-
Ruleset* parse_ruleset(Lookahead lookahead);
226+
Ruleset* parse_ruleset(Lookahead lookahead, bool is_root = false);
227227
Selector_Schema* parse_selector_schema(const char* end_of_selector);
228228
Selector_List* parse_selector_list(bool at_root = false);
229229
Complex_Selector* parse_complex_selector(bool in_root = true);
@@ -234,8 +234,8 @@ namespace Sass {
234234
Attribute_Selector* parse_attribute_selector();
235235
Block* parse_block(bool is_root = false);
236236
Block* parse_css_block(bool is_root = false);
237-
bool parse_block_nodes();
238-
bool parse_block_node();
237+
bool parse_block_nodes(bool is_root = false);
238+
bool parse_block_node(bool is_root = false);
239239

240240
bool parse_number_prefix();
241241
Declaration* parse_declaration();

0 commit comments

Comments
 (0)