Skip to content

Commit

Permalink
Fix some infinite mixin loops.
Browse files Browse the repository at this point in the history
  • Loading branch information
nex3 committed Dec 16, 2011
1 parent 1418331 commit 645d170
Show file tree
Hide file tree
Showing 4 changed files with 30 additions and 7 deletions.
2 changes: 2 additions & 0 deletions doc-src/SASS_CHANGELOG.md
Expand Up @@ -7,6 +7,8 @@

* Compatibility with the `mathn` library (thanks to [Thomas Walpole](https://github.com/twalpole)).

* Fix some infinite loops with mixins that were previously uncaught.

## 3.1.11

* Allow control directives (such as `@if`) to be nested beneath properties.
Expand Down
13 changes: 9 additions & 4 deletions lib/sass/environment.rb
Expand Up @@ -59,7 +59,7 @@ def push_frame(frame_info)
else
stack.push(top_of_stack = frame_info)
end
mixins_in_use << top_of_stack[:mixin] if top_of_stack[:mixin] && !top_of_stack[:prepared]
mixins_in_use << top_of_stack[:mixin] if top_of_stack[:mixin]
end

# Like \{#push\_frame}, but next time a stack frame is pushed,
Expand All @@ -72,9 +72,8 @@ def prepare_frame(frame_info)

# Pop a stack frame from the mixin/include stack.
def pop_frame
stack.pop if stack.last && stack.last[:prepared]
popped = stack.pop
mixins_in_use.delete(popped[:mixin]) if popped && popped[:mixin]
pop_and_unuse if stack.last && stack.last[:prepared]
pop_and_unuse
end

# A list of stack frames in the mixin/include stack.
Expand Down Expand Up @@ -106,6 +105,12 @@ def stack_trace

private

def pop_and_unuse
popped = stack.pop
mixins_in_use.delete(popped[:mixin]) if popped && popped[:mixin]
popped
end

def parent_options
@parent_options ||= @parent && @parent.options
end
Expand Down
4 changes: 1 addition & 3 deletions lib/sass/tree/visitors/perform.rb
Expand Up @@ -293,9 +293,7 @@ def run_interp(text)
def handle_include_loop!(node)
msg = "An @include loop has been found:"
mixins = @environment.stack.map {|s| s[:mixin]}.compact
if mixins.size == 2 && mixins[0] == mixins[1]
raise Sass::SyntaxError.new("#{msg} #{node.name} includes itself")
end
raise Sass::SyntaxError.new("#{msg} #{node.name} includes itself") if mixins.size == 1

mixins << node.name
msg << "\n" << Sass::Util.enum_cons(mixins, 2).map do |m1, m2|
Expand Down
18 changes: 18 additions & 0 deletions test/sass/engine_test.rb
Expand Up @@ -2041,6 +2041,24 @@ def test_media_with_parent_references

# Regression tests

def test_tricky_mixin_loop_exception
render <<SASS
@mixin foo($a)
@if $a
@include foo(false)
@include foo(true)
@else
a: b
a
@include foo(true)
SASS
assert(false, "Exception not raised")
rescue Sass::SyntaxError => err
assert_equal("An @include loop has been found: foo includes itself", err.message)
assert_hash_has(err.sass_backtrace[0], :mixin => "foo", :line => 3)
end

def test_interpolated_comment_in_mixin
assert_equal <<CSS, render(<<SASS)
/* color: red */
Expand Down

0 comments on commit 645d170

Please sign in to comment.