From 8741d13612b371a7a7b903c0e3ff2315876a85d7 Mon Sep 17 00:00:00 2001
From: evilebottnawi <sheo13666q@gmail.com>
Date: Wed, 11 Sep 2019 17:07:53 +0300
Subject: [PATCH] fix: invalid parsing of traitalias

---
 src/ast/traitalias.js                         |  18 +-
 src/parser/class.js                           | 107 +++--
 .../__snapshots__/graceful.test.js.snap       |   4 +-
 .../__snapshots__/traitalias.test.js.snap     | 388 ++++++++++++++++++
 test/snapshot/traitalias.test.js              | 123 ++++++
 5 files changed, 605 insertions(+), 35 deletions(-)
 create mode 100644 test/snapshot/__snapshots__/traitalias.test.js.snap
 create mode 100644 test/snapshot/traitalias.test.js

diff --git a/src/ast/traitalias.js b/src/ast/traitalias.js
index 32214375e..f85c3c393 100644
--- a/src/ast/traitalias.js
+++ b/src/ast/traitalias.js
@@ -26,7 +26,7 @@ module.exports = Node.extends(KIND, function TraitAlias(
   trait,
   method,
   as,
-  flags,
+  visibility,
   docs,
   location
 ) {
@@ -34,14 +34,18 @@ module.exports = Node.extends(KIND, function TraitAlias(
   this.trait = trait;
   this.method = method;
   this.as = as;
-  this.visibility = IS_UNDEFINED;
-  if (flags) {
-    if (flags[0] === 0) {
+
+  switch (visibility) {
+    case 0:
       this.visibility = IS_PUBLIC;
-    } else if (flags[0] === 1) {
+      break;
+    case 1:
       this.visibility = IS_PROTECTED;
-    } else if (flags[0] === 2) {
+      break;
+    case 2:
       this.visibility = IS_PRIVATE;
-    }
+      break;
+    default:
+      this.visibility = IS_UNDEFINED;
   }
 });
diff --git a/src/parser/class.js b/src/parser/class.js
index c86d455bf..ce30a9d33 100644
--- a/src/parser/class.js
+++ b/src/parser/class.js
@@ -203,6 +203,40 @@ module.exports = {
 
     return result(null, items, flags);
   },
+
+  read_member_modifier: function() {
+    let modifier;
+
+    switch (this.token) {
+      case this.tok.T_PUBLIC:
+        modifier = 0;
+        break;
+      case this.tok.T_PROTECTED:
+        modifier = 1;
+        break;
+      case this.tok.T_PRIVATE:
+        modifier = 2;
+        break;
+      case this.tok.T_STATIC:
+        modifier = 3;
+        break;
+      case this.tok.T_ABSTRACT:
+        modifier = 4;
+        break;
+      case this.tok.T_FINAL:
+        modifier = 5;
+        break;
+      default: {
+        const err = this.error("T_MEMBER_FLAGS");
+        this.next();
+        return err;
+      }
+    }
+
+    this.next();
+    return modifier;
+  },
+
   /**
    * Read member flags
    * @return array
@@ -213,31 +247,34 @@ module.exports = {
   read_member_flags: function(asInterface) {
     const result = [-1, -1, -1];
     if (this.is("T_MEMBER_FLAGS")) {
-      let idx = 0,
-        val = 0;
       do {
-        switch (this.token) {
-          case this.tok.T_PUBLIC:
+        let idx = 0;
+        let val = 0;
+
+        const visibility = this.read_member_modifier();
+
+        switch (visibility) {
+          case 0:
             idx = 0;
             val = 0;
             break;
-          case this.tok.T_PROTECTED:
+          case 1:
             idx = 0;
             val = 1;
             break;
-          case this.tok.T_PRIVATE:
+          case 2:
             idx = 0;
             val = 2;
             break;
-          case this.tok.T_STATIC:
+          case 3:
             idx = 1;
             val = 1;
             break;
-          case this.tok.T_ABSTRACT:
+          case 4:
             idx = 2;
             val = 1;
             break;
-          case this.tok.T_FINAL:
+          case 5:
             idx = 2;
             val = 2;
             break;
@@ -259,7 +296,7 @@ module.exports = {
         } else if (val !== -1) {
           result[idx] = val;
         }
-      } while (this.next().is("T_MEMBER_FLAGS"));
+      } while (this.is("T_MEMBER_FLAGS"));
     }
 
     if (result[1] == -1) result[1] = 0;
@@ -372,18 +409,18 @@ module.exports = {
     const node = this.node("traituse");
     this.expect(this.tok.T_USE) && this.next();
     const traits = [this.read_namespace_name()];
-    let adaptations = null;
     while (this.token === ",") {
       traits.push(this.next().read_namespace_name());
     }
+    const adaptations = this.read_trait_adaptations();
+    return node(traits, adaptations);
+  },
+
+  read_trait_adaptations: function() {
+    let adaptations = null;
+
     if (this.token === "{") {
-      adaptations = [];
-      // defines alias statements
-      while (this.next().token !== this.EOF) {
-        if (this.token === "}") break;
-        adaptations.push(this.read_trait_use_alias());
-        this.expect(";");
-      }
+      adaptations = this.read_trait_adaptation_list();
       if (this.expect("}")) {
         this.next();
       }
@@ -392,17 +429,34 @@ module.exports = {
         this.next();
       }
     }
-    return node(traits, adaptations);
+
+    return adaptations;
   },
+
+  /*
+   * Reads trait adaptation list
+   */
+  read_trait_adaptation_list: function() {
+    let adaptations = [];
+    // defines alias statements
+    while (this.next().token !== this.EOF) {
+      if (this.token === "}") break;
+      adaptations.push(this.read_trait_adaptation());
+      this.expect(";");
+    }
+
+    return adaptations;
+  },
+
   /**
-   * Reading trait alias
+   * Reading trait adaptation
    * ```ebnf
    * trait_use_alias ::= namespace_name ( T_DOUBLE_COLON T_STRING )? (T_INSTEADOF namespace_name) | (T_AS member_flags? T_STRING)
    * ```
    * name list : https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y#L303
    * trait adaptation : https://github.com/php/php-src/blob/master/Zend/zend_language_parser.y#L742
    */
-  read_trait_use_alias: function() {
+  read_trait_adaptation: function() {
     const node = this.node();
     let trait = null;
     let method;
@@ -445,10 +499,11 @@ module.exports = {
       );
     } else if (this.token === this.tok.T_AS) {
       // handle trait alias
-      let flags = null;
+      let visibility = null;
       let alias = null;
-      if (this.next().is("T_MEMBER_FLAGS")) {
-        flags = this.read_member_flags();
+      this.next();
+      if (this.is("T_MEMBER_FLAGS")) {
+        visibility = this.read_member_modifier();
       }
 
       if (
@@ -459,12 +514,12 @@ module.exports = {
         const name = this.text();
         this.next();
         alias = alias(name);
-      } else if (flags === false) {
+      } else if (visibility === false) {
         // no visibility flags and no name => too bad
         this.expect(this.tok.T_STRING);
       }
 
-      return node("traitalias", trait, method, alias, flags);
+      return node("traitalias", trait, method, alias, visibility);
     }
 
     // handle errors
diff --git a/test/snapshot/__snapshots__/graceful.test.js.snap b/test/snapshot/__snapshots__/graceful.test.js.snap
index d6ae2966e..caaaf5f61 100644
--- a/test/snapshot/__snapshots__/graceful.test.js.snap
+++ b/test/snapshot/__snapshots__/graceful.test.js.snap
@@ -575,8 +575,8 @@ Program {
       "expected": undefined,
       "kind": "error",
       "line": 3,
-      "message": "Parse Error : syntax error, unexpected 'abstract' (T_ABSTRACT) on line 3",
-      "token": "'abstract' (T_ABSTRACT)",
+      "message": "Parse Error : syntax error, unexpected 'function' (T_FUNCTION) on line 3",
+      "token": "'function' (T_FUNCTION)",
     },
     Error {
       "expected": ";",
diff --git a/test/snapshot/__snapshots__/traitalias.test.js.snap b/test/snapshot/__snapshots__/traitalias.test.js.snap
new file mode 100644
index 000000000..3a2a8adcd
--- /dev/null
+++ b/test/snapshot/__snapshots__/traitalias.test.js.snap
@@ -0,0 +1,388 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`traitalias as abstract 1`] = `
+Program {
+  "children": Array [
+    Class {
+      "body": Array [
+        TraitUse {
+          "adaptations": Array [
+            TraitAlias {
+              "as": null,
+              "kind": "traitalias",
+              "method": "sayHello",
+              "trait": null,
+              "visibility": "",
+            },
+          ],
+          "kind": "traituse",
+          "traits": Array [
+            ClassReference {
+              "kind": "classreference",
+              "name": "HelloWorld",
+              "resolution": "uqn",
+            },
+          ],
+        },
+      ],
+      "extends": null,
+      "implements": null,
+      "isAbstract": false,
+      "isAnonymous": false,
+      "isFinal": false,
+      "kind": "class",
+      "name": Identifier {
+        "kind": "identifier",
+        "name": "MyClass1",
+      },
+    },
+  ],
+  "errors": Array [],
+  "kind": "program",
+}
+`;
+
+exports[`traitalias as final 1`] = `
+Program {
+  "children": Array [
+    Class {
+      "body": Array [
+        TraitUse {
+          "adaptations": Array [
+            TraitAlias {
+              "as": null,
+              "kind": "traitalias",
+              "method": "sayHello",
+              "trait": null,
+              "visibility": "",
+            },
+          ],
+          "kind": "traituse",
+          "traits": Array [
+            ClassReference {
+              "kind": "classreference",
+              "name": "HelloWorld",
+              "resolution": "uqn",
+            },
+          ],
+        },
+      ],
+      "extends": null,
+      "implements": null,
+      "isAbstract": false,
+      "isAnonymous": false,
+      "isFinal": false,
+      "kind": "class",
+      "name": Identifier {
+        "kind": "identifier",
+        "name": "MyClass1",
+      },
+    },
+  ],
+  "errors": Array [],
+  "kind": "program",
+}
+`;
+
+exports[`traitalias as method 1`] = `
+Program {
+  "children": Array [
+    Class {
+      "body": Array [
+        TraitUse {
+          "adaptations": Array [
+            TraitAlias {
+              "as": Identifier {
+                "kind": "identifier",
+                "name": "myPrivateHello",
+              },
+              "kind": "traitalias",
+              "method": "sayHello",
+              "trait": null,
+              "visibility": "",
+            },
+          ],
+          "kind": "traituse",
+          "traits": Array [
+            ClassReference {
+              "kind": "classreference",
+              "name": "HelloWorld",
+              "resolution": "uqn",
+            },
+          ],
+        },
+      ],
+      "extends": null,
+      "implements": null,
+      "isAbstract": false,
+      "isAnonymous": false,
+      "isFinal": false,
+      "kind": "class",
+      "name": Identifier {
+        "kind": "identifier",
+        "name": "MyClass2",
+      },
+    },
+  ],
+  "errors": Array [],
+  "kind": "program",
+}
+`;
+
+exports[`traitalias as private 1`] = `
+Program {
+  "children": Array [
+    Class {
+      "body": Array [
+        TraitUse {
+          "adaptations": Array [
+            TraitAlias {
+              "as": null,
+              "kind": "traitalias",
+              "method": "sayHello",
+              "trait": null,
+              "visibility": "private",
+            },
+          ],
+          "kind": "traituse",
+          "traits": Array [
+            ClassReference {
+              "kind": "classreference",
+              "name": "HelloWorld",
+              "resolution": "uqn",
+            },
+          ],
+        },
+      ],
+      "extends": null,
+      "implements": null,
+      "isAbstract": false,
+      "isAnonymous": false,
+      "isFinal": false,
+      "kind": "class",
+      "name": Identifier {
+        "kind": "identifier",
+        "name": "MyClass1",
+      },
+    },
+  ],
+  "errors": Array [],
+  "kind": "program",
+}
+`;
+
+exports[`traitalias as protected 1`] = `
+Program {
+  "children": Array [
+    Class {
+      "body": Array [
+        TraitUse {
+          "adaptations": Array [
+            TraitAlias {
+              "as": null,
+              "kind": "traitalias",
+              "method": "sayHello",
+              "trait": null,
+              "visibility": "protected",
+            },
+          ],
+          "kind": "traituse",
+          "traits": Array [
+            ClassReference {
+              "kind": "classreference",
+              "name": "HelloWorld",
+              "resolution": "uqn",
+            },
+          ],
+        },
+      ],
+      "extends": null,
+      "implements": null,
+      "isAbstract": false,
+      "isAnonymous": false,
+      "isFinal": false,
+      "kind": "class",
+      "name": Identifier {
+        "kind": "identifier",
+        "name": "MyClass1",
+      },
+    },
+  ],
+  "errors": Array [],
+  "kind": "program",
+}
+`;
+
+exports[`traitalias as protected protected 1`] = `
+Program {
+  "children": Array [
+    Class {
+      "body": Array [
+        TraitUse {
+          "adaptations": Array [
+            TraitAlias {
+              "as": Identifier {
+                "kind": "identifier",
+                "name": "protected",
+              },
+              "kind": "traitalias",
+              "method": "sayHello",
+              "trait": null,
+              "visibility": "protected",
+            },
+          ],
+          "kind": "traituse",
+          "traits": Array [
+            ClassReference {
+              "kind": "classreference",
+              "name": "HelloWorld",
+              "resolution": "uqn",
+            },
+          ],
+        },
+      ],
+      "extends": null,
+      "implements": null,
+      "isAbstract": false,
+      "isAnonymous": false,
+      "isFinal": false,
+      "kind": "class",
+      "name": Identifier {
+        "kind": "identifier",
+        "name": "MyClass1",
+      },
+    },
+  ],
+  "errors": Array [],
+  "kind": "program",
+}
+`;
+
+exports[`traitalias as public 1`] = `
+Program {
+  "children": Array [
+    Class {
+      "body": Array [
+        TraitUse {
+          "adaptations": Array [
+            TraitAlias {
+              "as": null,
+              "kind": "traitalias",
+              "method": "sayHello",
+              "trait": null,
+              "visibility": "public",
+            },
+          ],
+          "kind": "traituse",
+          "traits": Array [
+            ClassReference {
+              "kind": "classreference",
+              "name": "HelloWorld",
+              "resolution": "uqn",
+            },
+          ],
+        },
+      ],
+      "extends": null,
+      "implements": null,
+      "isAbstract": false,
+      "isAnonymous": false,
+      "isFinal": false,
+      "kind": "class",
+      "name": Identifier {
+        "kind": "identifier",
+        "name": "MyClass1",
+      },
+    },
+  ],
+  "errors": Array [],
+  "kind": "program",
+}
+`;
+
+exports[`traitalias as public with method 1`] = `
+Program {
+  "children": Array [
+    Class {
+      "body": Array [
+        TraitUse {
+          "adaptations": Array [
+            TraitAlias {
+              "as": Identifier {
+                "kind": "identifier",
+                "name": "myPrivateHello",
+              },
+              "kind": "traitalias",
+              "method": "sayHello",
+              "trait": null,
+              "visibility": "public",
+            },
+          ],
+          "kind": "traituse",
+          "traits": Array [
+            ClassReference {
+              "kind": "classreference",
+              "name": "HelloWorld",
+              "resolution": "uqn",
+            },
+          ],
+        },
+      ],
+      "extends": null,
+      "implements": null,
+      "isAbstract": false,
+      "isAnonymous": false,
+      "isFinal": false,
+      "kind": "class",
+      "name": Identifier {
+        "kind": "identifier",
+        "name": "MyClass2",
+      },
+    },
+  ],
+  "errors": Array [],
+  "kind": "program",
+}
+`;
+
+exports[`traitalias as static 1`] = `
+Program {
+  "children": Array [
+    Class {
+      "body": Array [
+        TraitUse {
+          "adaptations": Array [
+            TraitAlias {
+              "as": null,
+              "kind": "traitalias",
+              "method": "sayHello",
+              "trait": null,
+              "visibility": "",
+            },
+          ],
+          "kind": "traituse",
+          "traits": Array [
+            ClassReference {
+              "kind": "classreference",
+              "name": "HelloWorld",
+              "resolution": "uqn",
+            },
+          ],
+        },
+      ],
+      "extends": null,
+      "implements": null,
+      "isAbstract": false,
+      "isAnonymous": false,
+      "isFinal": false,
+      "kind": "class",
+      "name": Identifier {
+        "kind": "identifier",
+        "name": "MyClass1",
+      },
+    },
+  ],
+  "errors": Array [],
+  "kind": "program",
+}
+`;
diff --git a/test/snapshot/traitalias.test.js b/test/snapshot/traitalias.test.js
new file mode 100644
index 000000000..a3c30a021
--- /dev/null
+++ b/test/snapshot/traitalias.test.js
@@ -0,0 +1,123 @@
+const parser = require("../main");
+
+describe("traitalias", function() {
+  it("as public with method", function() {
+    expect(
+      parser.parseEval(`
+class MyClass2 {
+    use HelloWorld { sayHello as public myPrivateHello; }
+}
+`)
+    ).toMatchSnapshot();
+  });
+
+  it("as method", function() {
+    expect(
+      parser.parseEval(`
+class MyClass2 {
+    use HelloWorld { sayHello as myPrivateHello; }
+}
+`)
+    ).toMatchSnapshot();
+  });
+
+  it("as public", function() {
+    expect(
+      parser.parseEval(`
+class MyClass1 {
+    use HelloWorld { sayHello as public; }
+}
+`)
+    ).toMatchSnapshot();
+  });
+
+  it("as protected", function() {
+    expect(
+      parser.parseEval(`
+class MyClass1 {
+    use HelloWorld { sayHello as protected; }
+}
+`)
+    ).toMatchSnapshot();
+  });
+
+  it("as private", function() {
+    expect(
+      parser.parseEval(`
+class MyClass1 {
+    use HelloWorld { sayHello as private; }
+}
+`)
+    ).toMatchSnapshot();
+  });
+
+  // PHP Fatal error:  Cannot use 'static' as method modifier
+  // but should be parsable, because allowed by grammar
+  it("as static", function() {
+    const astErr = parser.parseEval(
+      `
+class MyClass1 {
+    use HelloWorld { sayHello as static; }
+}
+`,
+      {
+        parser: {
+          suppressErrors: true
+        }
+      }
+    );
+    expect(astErr).toMatchSnapshot();
+  });
+
+  // PHP Fatal error:  Cannot use 'abstract' as method modifier
+  // but should be parsable, because allowed by grammar
+  it("as abstract", function() {
+    const astErr = parser.parseEval(
+      `
+class MyClass1 {
+    use HelloWorld { sayHello as abstract; }
+}
+`,
+      {
+        parser: {
+          suppressErrors: true
+        }
+      }
+    );
+    expect(astErr).toMatchSnapshot();
+  });
+
+  // PHP Fatal error:  Cannot use 'final' as method modifier
+  // but should be parsable, because allowed by grammar
+  it("as final", function() {
+    const astErr = parser.parseEval(
+      `
+class MyClass1 {
+    use HelloWorld { sayHello as final; }
+}
+`,
+      {
+        parser: {
+          suppressErrors: true
+        }
+      }
+    );
+    expect(astErr).toMatchSnapshot();
+  });
+
+  it("as protected protected", function() {
+    const astErr = parser.parseEval(
+      `
+class MyClass1 {
+    use HelloWorld { sayHello as protected protected; }
+}
+`,
+      {
+        parser: {
+          suppressErrors: true
+        }
+      }
+    );
+    expect(astErr).toMatchSnapshot();
+  });
+});