class Pedant::CheckFlippedOperandsOnMatchOrSubstring

Public Class Methods

requires() click to toggle source
Calls superclass method Pedant::Check::requires
# File lib/pedant/checks/flipped_operands_on_match_or_substring.rb, line 29
def self.requires
  super + [:trees]
end

Public Instance Methods

check(file, tree) click to toggle source
# File lib/pedant/checks/flipped_operands_on_match_or_substring.rb, line 33
def check(file, tree)
  def walk(node, root)
    def is_get_kb_item_with_literal? node
      # Is this a call to get_kb_item with just one literal string?
      return true if node.is_a?(Nasl::Call) and
        node.name.ident.name == 'get_kb_item' and
        node.name.indexes == [] and
        node.args.length == 1 and
        node.args.first.expr.is_a?(Nasl::String)

      return false
    end
    # Recursively descend into the right-hand and left-hand sides of each expression.
    if node.is_a? Nasl::Expression
      [:lhs, :rhs].each { |side| walk(node.send(side), root) }

      return unless node.op.is_a?(Nasl::Token)

      # We have four operators we examine here:
      # The regex operators, and the substring operators. One operand is the "needle" and the other is the "haystack".
      # For substring (><, >!<), the operands go: "needle" >< "the needle in the haystack"
      # For regex, it's the opposite: "the needle in the haystack" =~ "needle"
      # It's a common error to flip these by accident.

      # For the regex operators, the left side is what is being tested and
      # the right side is the pattern to match.
      if ["=~", "!~"].include?(node.op.body)
        side = :lhs
        opposite = :rhs
      end

      # The substring operators have their operands reversed from the regex
      # operators; the right side is the thing we are testing and the left
      # side is the pattern.
      if ["><", ">!<"].include?(node.op.body)
        side = :rhs
        opposite = :lhs
      end

      # If the operator isn't one of the four we check
      return if side.nil?

      # The check for no indexes is to account for this uncommon-but-in-use
      # pattern, to check that a character falls into a certain subset of
      # acceptable characters:
      #
      #   tolower(xml[index]) >< "abcdefghijklmnopqrstuvwxyz:_"
      #
      if node.send(side).is_a?(Nasl::String) && (node.send(opposite).is_a?(Nasl::Lvalue) && node.send(opposite).indexes == []) or is_get_kb_item_with_literal?(node.send(opposite))
        warn
        report(:error, "A '#{node.op.body}' operator has a literal string on the #{if side == :lhs then 'left' else 'right' end}-hand side.")
        report(:error, "The operands may be accidentally swapped.")
        report(:error, node.send(side).context(node))
      end
    end
  end

  cond_stmts = [:For, :Repeat, :While, :If].map { |cls| tree.all(cls) }.flatten
  cond_stmts.each { |cond_stmt| walk(cond_stmt.cond, cond_stmt) }
end
is_get_kb_item_with_literal?(node) click to toggle source
# File lib/pedant/checks/flipped_operands_on_match_or_substring.rb, line 35
def is_get_kb_item_with_literal? node
  # Is this a call to get_kb_item with just one literal string?
  return true if node.is_a?(Nasl::Call) and
    node.name.ident.name == 'get_kb_item' and
    node.name.indexes == [] and
    node.args.length == 1 and
    node.args.first.expr.is_a?(Nasl::String)

  return false
end
run() click to toggle source
# File lib/pedant/checks/flipped_operands_on_match_or_substring.rb, line 94
def run
  # This check will pass by default.
  pass

  # Run this check on the tree from every file.
  @kb[:trees].each { |file, tree| check(file, tree) }
end
walk(node, root) click to toggle source
# File lib/pedant/checks/flipped_operands_on_match_or_substring.rb, line 34
def walk(node, root)
  def is_get_kb_item_with_literal? node
    # Is this a call to get_kb_item with just one literal string?
    return true if node.is_a?(Nasl::Call) and
      node.name.ident.name == 'get_kb_item' and
      node.name.indexes == [] and
      node.args.length == 1 and
      node.args.first.expr.is_a?(Nasl::String)

    return false
  end
  # Recursively descend into the right-hand and left-hand sides of each expression.
  if node.is_a? Nasl::Expression
    [:lhs, :rhs].each { |side| walk(node.send(side), root) }

    return unless node.op.is_a?(Nasl::Token)

    # We have four operators we examine here:
    # The regex operators, and the substring operators. One operand is the "needle" and the other is the "haystack".
    # For substring (><, >!<), the operands go: "needle" >< "the needle in the haystack"
    # For regex, it's the opposite: "the needle in the haystack" =~ "needle"
    # It's a common error to flip these by accident.

    # For the regex operators, the left side is what is being tested and
    # the right side is the pattern to match.
    if ["=~", "!~"].include?(node.op.body)
      side = :lhs
      opposite = :rhs
    end

    # The substring operators have their operands reversed from the regex
    # operators; the right side is the thing we are testing and the left
    # side is the pattern.
    if ["><", ">!<"].include?(node.op.body)
      side = :rhs
      opposite = :lhs
    end

    # If the operator isn't one of the four we check
    return if side.nil?

    # The check for no indexes is to account for this uncommon-but-in-use
    # pattern, to check that a character falls into a certain subset of
    # acceptable characters:
    #
    #   tolower(xml[index]) >< "abcdefghijklmnopqrstuvwxyz:_"
    #
    if node.send(side).is_a?(Nasl::String) && (node.send(opposite).is_a?(Nasl::Lvalue) && node.send(opposite).indexes == []) or is_get_kb_item_with_literal?(node.send(opposite))
      warn
      report(:error, "A '#{node.op.body}' operator has a literal string on the #{if side == :lhs then 'left' else 'right' end}-hand side.")
      report(:error, "The operands may be accidentally swapped.")
      report(:error, node.send(side).context(node))
    end
  end
end