Skip to content

Type cast causes parentheses to be omitted in dynamically typed languages #1228

Open
@generalmimon

Description

@generalmimon

This is a KSC 0.11-SNAPSHOT regression - this worked in 0.10, but is broken in 91aeb53e (the latest commit at master at the time of writing).

Related to #69

I noticed this first in common/vlq_base128_le.ksy, more specifically on the value_signed value instance. It is defined like this:

common/vlq_base128_le.ksy:61-62

  value_signed:
    value: '((value ^ sign_bit).as<s8> - sign_bit.as<s8>).as<s8>'

However, KSC at commit 91aeb53e (i.e. the latest KSC 0.11-SNAPSHOT at the time of writing) translates it as follows:

  Object.defineProperty(VlqBase128Le.prototype, 'valueSigned', {
    get: function() {
      if (this._m_valueSigned !== undefined)
        return this._m_valueSigned;
      this._m_valueSigned = this.value ^ this.signBit - this.signBit;
      return this._m_valueSigned;
    }
  });

This is wrong and leads to incorrect results. In JavaScript, the bitwise XOR operator (^) has a lower precedence than the subtraction operator (-), so it will be evaluated like this:

  value_signed:
    value: 'value ^ (sign_bit - sign_bit)'

Which is obviously very different: since (sign_bit - sign_bit) is always 0, it's effectively value ^ 0, so value_signed will always be equal to the value attribute (which represents the unsigned value) without performing the intended sign extension.

This incorrect omission of essential parentheses is apparently caused by adding the type cast. Since kaitai-io/kaitai_struct_compiler#315, I knew that KSC knows operator precedence in individual target languages very well (because I have tested it extensively), so I was very surprised to find this bug.

The following simple .ksy snippet that I tested in the devel Web IDE shows that everything works fine without type cast, but once the type cast is applied to the parentheses, they disappear:

meta:
  id: cast_removes_parens
instances:
  no_cast:
    value: '(1 + 2) * 3'
  with_cast:
    value: '(1 + 2).as<s4> * 3'

Generated JavaScript code:

      this._m_noCast = (1 + 2) * 3;
      // ...
      this._m_withCast = 1 + 2 * 3;

It doesn't just apply to JavaScript - it applies to all dynamically typed languages (where type casting is a no-op by design):

  • Lua

      self._m_no_cast = (1 + 2) * 3
      -- ...
      self._m_with_cast = 1 + 2 * 3
  • Perl

        $self->{no_cast} = (1 + 2) * 3;
        # ...
        $self->{with_cast} = 1 + 2 * 3;
  • PHP

                $this->_m_noCast = (1 + 2) * 3;
                // ...
                $this->_m_withCast = 1 + 2 * 3;
  • Python

            self._m_no_cast = (1 + 2) * 3
            # ...
            self._m_with_cast = 1 + 2 * 3
  • Ruby

        @no_cast = (1 + 2) * 3
        # ...
        @with_cast = 1 + 2 * 3

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions