Commit fd281415 authored by Rene Saarsoo's avatar Rene Saarsoo
Browse files

Evaluate JavaScript strings without Ruby's eval().

Using eval() was a hack and caused a string like "#$%" to crash
JSDuck as Ruby tried to access the global $% variable.

Fixes #419
parent 48d4847e
Loading
Loading
Loading
Loading
+29 −2
Original line number Diff line number Diff line
@@ -65,7 +65,7 @@ module JsDuck
        when RKelly::Nodes::StringNode == node.class
          make(node, {
            "type" => "Literal",
            "value" => eval(node.value),
            "value" => string_value(node.value),
            "raw" => node.value,
          })
        when RKelly::Nodes::RegexpNode == node.class
@@ -227,7 +227,7 @@ module JsDuck
              elsif node.name =~ /['"]/
                {
                  "type" => "Literal",
                  "value" => eval(node.name),
                  "value" => string_value(node.name),
                  "raw" => node.name,
                  "range" => offset_range(node, :name),
                }
@@ -458,6 +458,33 @@ module JsDuck
        }
      end

      # Evaluates the actual value of a JavaScript string.
      # Importantly we avoid using Ruby's eval().
      def string_value(string)
        string[1..-2].gsub(/\\([0-9]{1,3}|x[0-9A-F]{1,2}|u[0-9A-F]{4}|.)/) do |s|
          if STRING_ESCAPES[s]
            STRING_ESCAPES[s]
          elsif s =~ /^\\[0-9]/
            s[1..-1].oct.chr
          elsif s =~ /^\\x[0-9A-F]/
            s[2..-1].hex.chr
          elsif s =~ /^\\u[0-9A-F]/
            [s[2..-1].hex].pack("U")
          else
            s[1, 1]
          end
        end
      end

      STRING_ESCAPES = {
        '\b' => "\b",
        '\f' => "\f",
        '\n' => "\n",
        '\r' => "\r",
        '\t' => "\t",
        '\v' => "\v",
      }

      BINARY_NODES = {
        RKelly::Nodes::SubtractNode => "-",
        RKelly::Nodes::LessOrEqualNode => "<=",
+102 −0
Original line number Diff line number Diff line
require "rkelly"
require "jsduck/js/rkelly_adapter"

describe JsDuck::Js::RKellyAdapter do
  def adapt(string)
    rkelly_ast = RKelly::Parser.new.parse(string)
    ast = JsDuck::Js::RKellyAdapter.new.adapt(rkelly_ast)
    return ast["body"][0]
  end

  def adapt_value(string)
    adapt(string)["expression"]["value"]
  end

  describe "values of numbers" do
    it "decimal" do
      adapt_value("5").should == 5
    end

    it "octal" do
      adapt_value("015").should == 8 + 5
    end

    it "hex" do
      adapt_value("0x1F").should == 16 + 15
    end

    it "float" do
      adapt_value("3.14").should == 3.14
    end

    it "float beginning with comma" do
      adapt_value(".15").should == 0.15
    end

    it "float with E" do
      adapt_value("2e12").should == 2000000000000
    end
  end

  describe "values of strings" do
    it "single-quoted" do
      adapt_value("'foo'").should == 'foo'
    end

    it "double-quoted" do
      adapt_value('"bar"').should == "bar"
    end

    it "with special chars" do
      adapt_value('"\n \t \r"').should == "\n \t \r"
    end

    it "with escaped quotes" do
      adapt_value('" \" "').should == ' " '
    end

    it "with latin1 octal escape" do
      adapt_value('"\101 \251"').should == "A \251"
    end

    it "with latin1 hex escape" do
      adapt_value('"\x41 \xA9"').should == "A \xA9"
    end

    it "with unicode escape" do
      adapt_value('"\u00A9"').should == [0x00A9].pack("U")
    end

    it "with Ruby-like variable interpolation" do
      adapt_value('"#{foo}"').should == '#{foo}'
    end
  end

  describe "values of regexes" do
    it "are left as is" do
      adapt_value('/blah.*/').should == '/blah.*/'
    end
  end

  describe "values of boolens" do
    it "true" do
      adapt_value('true').should == true
    end
    it "false" do
      adapt_value('false').should == false
    end
  end

  describe "value of null" do
    it "is nil" do
      adapt_value('null').should == nil
    end
  end

  describe "string properties" do
    it "don't use Ruby's eval()" do
      adapt('({"foo#$%": 5})')["expression"]["properties"][0]["key"]["value"].should == 'foo#$%'
    end
  end

end