]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Pull request #3209: Javascript de-aliasing
authorMike Stepanek (mstepane) <mstepane@cisco.com>
Mon, 13 Dec 2021 20:30:13 +0000 (20:30 +0000)
committerMike Stepanek (mstepane) <mstepane@cisco.com>
Mon, 13 Dec 2021 20:30:13 +0000 (20:30 +0000)
Merge in SNORT/snort3 from ~DKYRYLOV/snort3:js_norm_dealias to master

Squashed commit of the following:

commit 5e04885d2ea2c5a56a9c4c501070ff5abfcde21d
Author: dkyrylov <dkyrylov@cisco.com>
Date:   Wed Nov 17 18:39:08 2021 +0200

    http_inspect: add JavaScript builtin de-aliasing

src/utils/js_identifier_ctx.cc
src/utils/js_identifier_ctx.h
src/utils/js_tokenizer.h
src/utils/js_tokenizer.l
src/utils/test/CMakeLists.txt
src/utils/test/js_dealias_test.cc [new file with mode: 0644]
src/utils/test/js_normalizer_test.cc
src/utils/test/js_test_utils.cc [new file with mode: 0644]
src/utils/test/js_test_utils.h [new file with mode: 0644]

index 5dff0b0850d85bf6c99261102f1dcecc1924abb6..d830fb1f849fe3ffdf1ba9f7c64d027c6c9e0417 100644 (file)
@@ -122,7 +122,26 @@ void JSIdentifierCtx::reset()
     scopes.emplace_back(JSProgramScopeType::GLOBAL);
 }
 
-void JSIdentifierCtx::ProgramScope::add_alias(const char* alias, const std::string& value)
+void JSIdentifierCtx::add_alias(const char* alias, const std::string&& value)
+{
+    assert(alias);
+    assert(!scopes.empty());
+    scopes.back().add_alias(alias, std::move(value));
+}
+
+const char* JSIdentifierCtx::alias_lookup(const char* alias) const
+{
+    assert(alias);
+
+    for (auto it = scopes.rbegin(); it != scopes.rend(); ++it)
+    {
+        if (const char* value = it->get_alias_value(alias))
+            return value;
+    }
+    return nullptr;
+}
+
+void JSIdentifierCtx::ProgramScope::add_alias(const char* alias, const std::string&& value)
 {
     assert(alias);
     aliases[alias] = value;
@@ -143,25 +162,6 @@ const char* JSIdentifierCtx::ProgramScope::get_alias_value(const char* alias) co
 
 #ifdef CATCH_TEST_BUILD
 
-void JSIdentifierCtx::add_alias(const char* alias, const std::string& value)
-{
-    assert(alias);
-    assert(!scopes.empty());
-    scopes.back().add_alias(alias, value);
-}
-
-const char* JSIdentifierCtx::alias_lookup(const char* alias) const
-{
-    assert(alias);
-
-    for (auto it = scopes.rbegin(); it != scopes.rend(); ++it)
-    {
-        if (const char* value = it->get_alias_value(alias))
-            return value;
-    }
-    return nullptr;
-}
-
 bool JSIdentifierCtx::scope_check(const std::list<JSProgramScopeType>& compare) const
 {
     if (scopes.size() != compare.size())
index b692e466c7dc5fae10f4816435581d27dd94ebda..3517c09fda0544dceae9f868afbf4906aa985df2 100644 (file)
@@ -39,6 +39,8 @@ public:
     virtual ~JSIdentifierCtxBase() = default;
 
     virtual const char* substitute(const char* identifier) = 0;
+    virtual void add_alias(const char* alias, const std::string&& value) = 0;
+    virtual const char* alias_lookup(const char* alias) const = 0;
     virtual bool built_in(const char* identifier) const = 0;
 
     virtual bool scope_push(JSProgramScopeType) = 0;
@@ -56,6 +58,8 @@ public:
         const std::unordered_set<std::string>& ident_built_in);
 
     virtual const char* substitute(const char* identifier) override;
+    virtual void add_alias(const char* alias, const std::string&& value) override;
+    virtual const char* alias_lookup(const char* alias) const override;
     virtual bool built_in(const char* identifier) const override;
 
     virtual bool scope_push(JSProgramScopeType) override;
@@ -74,7 +78,7 @@ private:
     public:
         ProgramScope(JSProgramScopeType t) : t(t) {}
 
-        void add_alias(const char* alias, const std::string& value);
+        void add_alias(const char* alias, const std::string&& value);
         const char* get_alias_value(const char* alias) const;
 
         JSProgramScopeType type() const
@@ -95,10 +99,6 @@ private:
 // advanced program scope access for testing
 #ifdef CATCH_TEST_BUILD
 public:
-    // alias tracking
-    void add_alias(const char* alias, const std::string& value);
-    const char* alias_lookup(const char* alias) const;
-
     // compare scope list with the passed pattern
     bool scope_check(const std::list<JSProgramScopeType>& compare) const;
     const std::list<JSProgramScopeType> get_types() const;
index 476dd39554d21cfd54cae564643f9570709af679..1bac36b1511b466315b3fc87828dbf0f054f7be0 100644 (file)
@@ -117,6 +117,16 @@ private:
         ASI_GROUP_MAX
     };
 
+    enum AliasState
+    {
+        ALIAS_NONE = 0,
+        ALIAS_DEFINITION, // var a
+        ALIAS_PREFIX,     // var a +%possible PDU split%
+                          // to handle ambiguity between a++, a+=, and a + b
+        ALIAS_EQUALS,     // var a =
+        ALIAS_VALUE       // var a = eval
+    };
+
 public:
     enum JSRet
     {
@@ -162,6 +172,7 @@ private:
     JSRet do_operator_spacing();
     JSRet do_semicolon_insertion(ASIGroup current);
     JSRet do_identifier_substitution(const char* lexeme, bool id_part);
+    JSRet push_identifier(const char* ident);
     bool unescape(const char* lexeme);
     void process_punctuator(JSToken tok = PUNCTUATOR);
     void process_closing_brace();
@@ -199,11 +210,28 @@ private:
     static const char* m2str(ScopeMetaType);
     static bool is_operator(JSToken);
 
+    void dealias_clear_mutated(bool id_part);
+    void dealias_increment();
+    void dealias_identifier(bool id_part, bool assignment_start);
+    void dealias_reset();
+    void dealias_prefix_reset();
+    void dealias_equals(bool complex);
+    void dealias_append();
+    void dealias_finalize();
+
     static const char* p_scope_codes[];
 
     void* cur_buffer;
     void* tmp_buffer = nullptr;
     std::stringstream tmp;
+
+    std::stringstream aliased;
+    std::string alias;
+    std::string last_dealiased;
+    AliasState alias_state = ALIAS_NONE;
+    bool prefix_increment = false;
+    bool dealias_stored = false;
+
     uint8_t max_template_nesting;
     std::stack<uint16_t, std::vector<uint16_t>> brace_depth;
     JSToken token = UNDEFINED;
index 369cd0c1cc2230c0bb2ceedd8092b852136f4e23..35d6e5529989a2b5f5b10841bb201e95cace7f38 100644 (file)
@@ -1047,38 +1047,38 @@ ALL_UNICODE    [\0-\x7F]|[\xC2-\xDF][\x80-\xBF]|(\xE0[\xA0-\xBF]|[\xE1-\xEF][\x8
 <bcomm>{BLOCK_COMMENT_SKIP}         { }
 <bcomm><<EOF>>                      { RETURN(SCRIPT_CONTINUE) }
 
-       {LITERAL_DQ_STRING_START}    { EXEC(do_semicolon_insertion(ASI_GROUP_7)) EXEC(do_spacing(LITERAL)) ECHO; BEGIN(dqstr); set_ident_norm(true); }
-<dqstr>{LITERAL_DQ_STRING_END}      { ECHO; BEGIN(divop); }
+       {LITERAL_DQ_STRING_START}    { dealias_append(); EXEC(do_semicolon_insertion(ASI_GROUP_7)) EXEC(do_spacing(LITERAL)) ECHO; BEGIN(dqstr); set_ident_norm(true); }
+<dqstr>{LITERAL_DQ_STRING_END}      { dealias_append(); ECHO; BEGIN(divop); }
 <dqstr>{HTML_TAG_SCRIPT_CLOSE}      { BEGIN(regst); RETURN(CLOSING_TAG) }
 <dqstr>\\{CR}{LF}                   { }
 <dqstr>\\{LF}                       { }
 <dqstr>\\{CR}                       { }
 <dqstr>{LINE_TERMINATORS}           { BEGIN(regst); RETURN(BAD_TOKEN) }
-<dqstr>{LITERAL_DQ_STRING_SKIP}     { ECHO; }
-<dqstr>{LITERAL_DQ_STRING_TEXT}     { ECHO; }
+<dqstr>{LITERAL_DQ_STRING_SKIP}     { dealias_append(); ECHO; }
+<dqstr>{LITERAL_DQ_STRING_TEXT}     { dealias_append(); ECHO; }
 <dqstr><<EOF>>                      { RETURN(SCRIPT_CONTINUE) }
 
-       {LITERAL_SQ_STRING_START}    { EXEC(do_semicolon_insertion(ASI_GROUP_7)) EXEC(do_spacing(LITERAL)) ECHO; BEGIN(sqstr); set_ident_norm(true); }
-<sqstr>{LITERAL_SQ_STRING_END}      { ECHO; BEGIN(divop); }
+       {LITERAL_SQ_STRING_START}    { dealias_append(); EXEC(do_semicolon_insertion(ASI_GROUP_7)) EXEC(do_spacing(LITERAL)) ECHO; BEGIN(sqstr); set_ident_norm(true); }
+<sqstr>{LITERAL_SQ_STRING_END}      { dealias_append(); ECHO; BEGIN(divop); }
 <sqstr>{HTML_TAG_SCRIPT_CLOSE}      { BEGIN(regst); RETURN(CLOSING_TAG) }
 <sqstr>\\{CR}{LF}                   { }
 <sqstr>\\{LF}                       { }
 <sqstr>\\{CR}                       { }
 <sqstr>{LINE_TERMINATORS}           { BEGIN(regst); RETURN(BAD_TOKEN) }
-<sqstr>{LITERAL_SQ_STRING_SKIP}     { ECHO; }
-<sqstr>{LITERAL_SQ_STRING_TEXT}     { ECHO; }
+<sqstr>{LITERAL_SQ_STRING_SKIP}     { dealias_append(); ECHO; }
+<sqstr>{LITERAL_SQ_STRING_TEXT}     { dealias_append(); ECHO; }
 <sqstr><<EOF>>                      { RETURN(SCRIPT_CONTINUE) }
 
-       {LITERAL_TEMPLATE_START}                  { EXEC(do_semicolon_insertion(ASI_GROUP_7)) EXEC(do_spacing(LITERAL)) ECHO; BEGIN(tmpll); set_ident_norm(true); }
-<tmpll>(\\\\)*{LITERAL_TEMPLATE_END}             { ECHO; BEGIN(divop); }
-<tmpll>(\\\\)*{LITERAL_TEMPLATE_SUBST_START}     { EXEC(process_subst_open()) }
+       {LITERAL_TEMPLATE_START}                  { dealias_append(); EXEC(do_semicolon_insertion(ASI_GROUP_7)) EXEC(do_spacing(LITERAL)) ECHO; BEGIN(tmpll); set_ident_norm(true); }
+<tmpll>(\\\\)*{LITERAL_TEMPLATE_END}             { dealias_append(); ECHO; BEGIN(divop); }
+<tmpll>(\\\\)*{LITERAL_TEMPLATE_SUBST_START}     { EXEC(process_subst_open()) dealias_reset(); }
 <tmpll>{HTML_TAG_SCRIPT_CLOSE}                   { BEGIN(regst); RETURN(CLOSING_TAG) }
 <tmpll>(\\\\)*\\{LITERAL_TEMPLATE_SUBST_START}   | /* escaped template substitution */
 <tmpll>(\\\\)*\\{LITERAL_TEMPLATE_END}           | /* escaped backtick */
-<tmpll>{LITERAL_TEMPLATE_OTHER}                  { ECHO; }
+<tmpll>{LITERAL_TEMPLATE_OTHER}                  { dealias_append(); ECHO; }
 <tmpll><<EOF>>                                   { RETURN(SCRIPT_CONTINUE) }
 
-<regst>{LITERAL_REGEX_START}        { EXEC(do_semicolon_insertion(ASI_GROUP_7)) EXEC(do_spacing(LITERAL)) yyout << '/'; states_correct(1); yyless(1); BEGIN(regex); set_ident_norm(true); }
+<regst>{LITERAL_REGEX_START}        { dealias_reset(); EXEC(do_semicolon_insertion(ASI_GROUP_7)) EXEC(do_spacing(LITERAL)) yyout << '/'; states_correct(1); yyless(1); BEGIN(regex); set_ident_norm(true); }
 <regex>{LITERAL_REGEX_END}          { ECHO; BEGIN(divop); }
 <regex>{HTML_TAG_SCRIPT_CLOSE}      { BEGIN(regst); RETURN(CLOSING_TAG) }
 <regex>{LITERAL_REGEX_SKIP}         { ECHO; }
@@ -1089,30 +1089,30 @@ ALL_UNICODE    [\0-\x7F]|[\xC2-\xDF][\x80-\xBF]|(\xE0[\xA0-\xBF]|[\xE1-\xEF][\x8
 <regex><<EOF>>                      { RETURN(SCRIPT_CONTINUE) }
 
 <divop>{DIV_OPERATOR}               |
-<divop>{DIV_ASSIGNMENT_OPERATOR}    { previous_group = ASI_OTHER; ECHO; token = PUNCTUATOR; BEGIN(INITIAL); set_ident_norm(true); }
-
-{OPEN_BRACE}                        { EXEC(do_semicolon_insertion(ASI_GROUP_1)) if (meta_type() == ScopeMetaType::NOT_SET) { if (is_operator(token) || token == COLON || func_call()) set_meta_type(ScopeMetaType::OBJECT); else { set_meta_type(ScopeMetaType::BLOCK); EXEC(p_scope_push(meta_type())) } } EXEC(scope_push(BRACES)) if (!brace_depth.empty()) brace_depth.top()++; process_punctuator(); }
-{CLOSE_BRACE}                       { EXEC(do_semicolon_insertion(ASI_GROUP_2)) if (meta_type() != ScopeMetaType::NOT_SET) EXEC(p_scope_pop(meta_type())) EXEC(scope_pop(BRACES)) process_closing_brace(); set_ident_norm(true); }
-{OPEN_PARENTHESIS}                  { EXEC(do_semicolon_insertion(ASI_GROUP_3)) EXEC(scope_push(PARENTHESES)) if (token == IDENTIFIER || token == CLOSING_BRACKET || token == KEYWORD) set_func_call(true); process_punctuator(); }
-{CLOSE_PARENTHESIS}                 { bool f_call = func_call(); bool id_norm = ident_norm(); if (meta_type() != ScopeMetaType::NOT_SET) EXEC(p_scope_pop(meta_type())) EXEC(scope_pop(PARENTHESES)) if (!f_call) set_ident_norm(id_norm); if (block_param()) { previous_group = ASI_OTHER; set_block_param(false); } else { EXEC(do_semicolon_insertion(ASI_GROUP_5)) } ECHO; token = PUNCTUATOR; BEGIN(divop); }
-{OPEN_BRACKET}                      { EXEC(do_semicolon_insertion(ASI_GROUP_3)) EXEC(do_semicolon_insertion(ASI_GROUP_4)) EXEC(scope_push(BRACKETS)) process_punctuator(); }
-{CLOSE_BRACKET}                     { EXEC(do_semicolon_insertion(ASI_GROUP_4)) EXEC(scope_pop(BRACKETS)) ECHO; token = CLOSING_BRACKET; BEGIN(divop); }
-
-{PUNCTUATOR_PREFIX}                 { EXEC(do_semicolon_insertion(ASI_GROUP_10)) process_punctuator(); set_ident_norm(true); }
-{DOT_ACCESSOR}                      { previous_group = ASI_OTHER; ECHO; token = DOT; BEGIN(regst); }
-{PUNCTUATOR_ARROW}                  { previous_group = ASI_OTHER; process_punctuator(); set_ident_norm(true); if (meta_type() == ScopeMetaType::NOT_SET) { set_meta_type(ScopeMetaType::FUNCTION); EXEC(p_scope_push(meta_type())) } }
-{PUNCTUATOR_SEMICOLON}              { previous_group = ASI_OTHER; process_punctuator(); set_ident_norm(true); if (meta_type() != ScopeMetaType::NOT_SET) { EXEC(p_scope_pop(meta_type())) set_meta_type(ScopeMetaType::NOT_SET); } }
-{PUNCTUATOR_COLON}                  { previous_group = ASI_OTHER; process_punctuator(COLON); set_ident_norm(true); }
-{OPERATOR_COMPARISON}               { previous_group = ASI_OTHER; process_punctuator(OPERATOR_COMPARISON); set_ident_norm(true); }
-{OPERATOR_COMPLEX_ASSIGNMENT}       { previous_group = ASI_OTHER; process_punctuator(OPERATOR_COMPLEX_ASSIGNMENT); set_ident_norm(true); }
-{OPERATOR_LOGICAL}                  { previous_group = ASI_OTHER; process_punctuator(OPERATOR_LOGICAL); set_ident_norm(true); }
-{OPERATOR_SHIFT}                    { previous_group = ASI_OTHER; process_punctuator(OPERATOR_SHIFT); set_ident_norm(true); }
-{PUNCTUATOR_COMMA}                  { previous_group = ASI_OTHER; process_punctuator(); set_ident_norm(true); }
+<divop>{DIV_ASSIGNMENT_OPERATOR}    { dealias_equals(true); previous_group = ASI_OTHER; ECHO; token = PUNCTUATOR; BEGIN(INITIAL); set_ident_norm(true); }
+
+{OPEN_BRACE}                        { dealias_reset(); EXEC(do_semicolon_insertion(ASI_GROUP_1)) if (meta_type() == ScopeMetaType::NOT_SET) { if (is_operator(token) || token == COLON || func_call()) set_meta_type(ScopeMetaType::OBJECT); else { set_meta_type(ScopeMetaType::BLOCK); EXEC(p_scope_push(meta_type())) } } EXEC(scope_push(BRACES)) if (!brace_depth.empty()) brace_depth.top()++; process_punctuator(); }
+{CLOSE_BRACE}                       { dealias_clear_mutated(false); EXEC(do_semicolon_insertion(ASI_GROUP_2)) if (meta_type() != ScopeMetaType::NOT_SET) EXEC(p_scope_pop(meta_type())) EXEC(scope_pop(BRACES)) process_closing_brace(); set_ident_norm(true); }
+{OPEN_PARENTHESIS}                  { dealias_clear_mutated(true); dealias_reset(); EXEC(do_semicolon_insertion(ASI_GROUP_3)) EXEC(scope_push(PARENTHESES)) if (token == IDENTIFIER || token == CLOSING_BRACKET || token == KEYWORD) set_func_call(true); process_punctuator(); }
+{CLOSE_PARENTHESIS}                 { dealias_clear_mutated(false); dealias_reset(); bool f_call = func_call(); bool id_norm = ident_norm(); if (meta_type() != ScopeMetaType::NOT_SET) EXEC(p_scope_pop(meta_type())) EXEC(scope_pop(PARENTHESES)) if (!f_call) set_ident_norm(id_norm); if (block_param()) { previous_group = ASI_OTHER; set_block_param(false); } else { EXEC(do_semicolon_insertion(ASI_GROUP_5)) } ECHO; token = PUNCTUATOR; BEGIN(divop); }
+{OPEN_BRACKET}                      { dealias_clear_mutated(true); dealias_append(); EXEC(do_semicolon_insertion(ASI_GROUP_3)) EXEC(do_semicolon_insertion(ASI_GROUP_4)) EXEC(scope_push(BRACKETS)) process_punctuator(); }
+{CLOSE_BRACKET}                     { dealias_clear_mutated(false); dealias_append(); EXEC(do_semicolon_insertion(ASI_GROUP_4)) EXEC(scope_pop(BRACKETS)) ECHO; token = CLOSING_BRACKET; BEGIN(divop); }
+
+{PUNCTUATOR_PREFIX}                 { process_punctuator(); EXEC(do_semicolon_insertion(ASI_GROUP_10)) set_ident_norm(true); }
+{DOT_ACCESSOR}                      { dealias_clear_mutated(true); previous_group = ASI_OTHER; dealias_append(); ECHO; token = DOT; BEGIN(regst); }
+{PUNCTUATOR_ARROW}                  { dealias_clear_mutated(false); previous_group = ASI_OTHER; dealias_reset(); process_punctuator(); set_ident_norm(true); if (meta_type() == ScopeMetaType::NOT_SET) { set_meta_type(ScopeMetaType::FUNCTION); EXEC(p_scope_push(meta_type())) } }
+{PUNCTUATOR_SEMICOLON}              { dealias_clear_mutated(false); previous_group = ASI_OTHER; dealias_finalize(); process_punctuator(); set_ident_norm(true); if (meta_type() != ScopeMetaType::NOT_SET) { EXEC(p_scope_pop(meta_type())) set_meta_type(ScopeMetaType::NOT_SET); } }
+{PUNCTUATOR_COLON}                  { dealias_clear_mutated(false); previous_group = ASI_OTHER; dealias_reset(); process_punctuator(COLON); set_ident_norm(true); }
+{OPERATOR_COMPARISON}               { dealias_clear_mutated(false); previous_group = ASI_OTHER; dealias_prefix_reset(); process_punctuator(OPERATOR_COMPARISON); set_ident_norm(true); }
+{OPERATOR_COMPLEX_ASSIGNMENT}       { dealias_clear_mutated(false); previous_group = ASI_OTHER; dealias_equals(true); process_punctuator(OPERATOR_COMPLEX_ASSIGNMENT); set_ident_norm(true); }
+{OPERATOR_LOGICAL}                  { dealias_clear_mutated(false); previous_group = ASI_OTHER; dealias_prefix_reset(); process_punctuator(OPERATOR_LOGICAL); set_ident_norm(true); }
+{OPERATOR_SHIFT}                    { dealias_clear_mutated(false); previous_group = ASI_OTHER; dealias_prefix_reset(); process_punctuator(OPERATOR_SHIFT); set_ident_norm(true); }
+{PUNCTUATOR_COMMA}                  { dealias_clear_mutated(false); previous_group = ASI_OTHER; dealias_finalize(); process_punctuator(); set_ident_norm(true); }
 
 {USE_STRICT_DIRECTIVE}              { previous_group = ASI_OTHER; EXEC(do_spacing(DIRECTIVE)) ECHO; BEGIN(INITIAL); yyout << ';'; set_ident_norm(true); }
 {USE_STRICT_DIRECTIVE_SC}           { previous_group = ASI_OTHER; EXEC(do_spacing(DIRECTIVE)) ECHO; BEGIN(INITIAL); set_ident_norm(true); }
 
-{KEYWORD_VAR_DECL}                  { EXEC(do_semicolon_insertion(ASI_GROUP_10)) if (token != DOT) set_ident_norm(true); EXEC(do_spacing(KEYWORD_VAR_DECL)) ECHO; BEGIN(regst); }
+{KEYWORD_VAR_DECL}                  { EXEC(do_semicolon_insertion(ASI_GROUP_10)) if (token != DOT) set_ident_norm(true); alias_state = ALIAS_NONE; EXEC(do_spacing(KEYWORD_VAR_DECL)) ECHO; BEGIN(regst); }
 {KEYWORD_FUNCTION}                  { EXEC(do_semicolon_insertion(ASI_GROUP_10)) if (token != DOT) set_ident_norm(true); EXEC(do_spacing(KEYWORD_FUNCTION)) ECHO; BEGIN(regst); if (meta_type() == ScopeMetaType::NOT_SET) set_meta_type(ScopeMetaType::FUNCTION); }
 {KEYWORD_IF}                        |
 {KEYWORD_FOR}                       |
@@ -1126,15 +1126,29 @@ ALL_UNICODE    [\0-\x7F]|[\xC2-\xDF][\x80-\xBF]|(\xE0[\xA0-\xBF]|[\xE1-\xEF][\x8
 {KEYWORD_ELSE}                      |
 {KEYWORD_FINALLY}                   { EXEC(do_semicolon_insertion(ASI_GROUP_10)) if (token != DOT) set_ident_norm(true); EXEC(do_spacing(KEYWORD_BLOCK)) ECHO; BEGIN(regst); if (meta_type() == ScopeMetaType::NOT_SET) { set_meta_type(ScopeMetaType::BLOCK); EXEC(p_scope_push(meta_type())) } }
 {KEYWORD_DO}                        { EXEC(do_semicolon_insertion(ASI_GROUP_10)) if (token != DOT) set_ident_norm(true); EXEC(do_spacing(KEYWORD_BLOCK)) ECHO; BEGIN(regst); if (meta_type() == ScopeMetaType::NOT_SET) { set_meta_type(ScopeMetaType::BLOCK); EXEC(p_scope_push(meta_type())) } set_do_loop(true); }
-{KEYWORD_CLASS}                     { previous_group = ASI_OTHER; if (token != DOT) set_ident_norm(true); EXEC(do_spacing(KEYWORD_CLASS)) ECHO; BEGIN(regst); if (meta_type() == ScopeMetaType::NOT_SET) set_meta_type(ScopeMetaType::OBJECT); }
-{KEYWORD_OTHER}                     { previous_group = ASI_OTHER; if (token != DOT) set_ident_norm(true); EXEC(do_spacing(KEYWORD)) ECHO; BEGIN(regst); }
-
-{OPERATOR_ASSIGNMENT}               { previous_group = ASI_OTHER; process_punctuator(OPERATOR_ASSIGNMENT); set_ident_norm(true); }
-{OPERATOR_PREFIX}                   { EXEC(do_semicolon_insertion(ASI_GROUP_6)) EXEC(do_operator_spacing()) ECHO; BEGIN(divop); set_ident_norm(true); }
-{OPERATOR_INCR_DECR}                { EXEC(do_semicolon_insertion(ASI_GROUP_8)) EXEC(do_operator_spacing()) ECHO; BEGIN(divop); set_ident_norm(true); }
-{OPERATOR}                          { previous_group = ASI_OTHER; EXEC(do_operator_spacing()) ECHO; BEGIN(divop); set_ident_norm(true); }
-{LITERAL}                           { EXEC(do_semicolon_insertion(ASI_GROUP_7)) EXEC(do_spacing(LITERAL)) ECHO; BEGIN(divop); set_ident_norm(true); }
-{IDENTIFIER}                        { EXEC(do_semicolon_insertion(ASI_GROUP_7)) if (unescape(YYText())) { bool id_part = (token == DOT); EXEC(do_spacing(IDENTIFIER)) EXEC(do_identifier_substitution(YYText(), id_part)) } BEGIN(divop); }
+{KEYWORD_CLASS}                     { previous_group = ASI_OTHER; dealias_reset(); if (token != DOT) set_ident_norm(true); EXEC(do_spacing(KEYWORD_CLASS)) ECHO; BEGIN(regst); if (meta_type() == ScopeMetaType::NOT_SET) set_meta_type(ScopeMetaType::OBJECT); }
+{KEYWORD_OTHER}                     { previous_group = ASI_OTHER; dealias_reset(); if (token != DOT) set_ident_norm(true); EXEC(do_spacing(KEYWORD)) ECHO; BEGIN(regst); }
+
+{OPERATOR_ASSIGNMENT}               { previous_group = ASI_OTHER; dealias_equals(false); process_punctuator(OPERATOR_ASSIGNMENT); set_ident_norm(true); }
+{OPERATOR_PREFIX}                   { dealias_prefix_reset(); EXEC(do_semicolon_insertion(ASI_GROUP_6)) EXEC(do_operator_spacing()) ECHO; BEGIN(divop); set_ident_norm(true); }
+{OPERATOR_INCR_DECR}                { dealias_increment(); dealias_reset(); EXEC(do_semicolon_insertion(ASI_GROUP_8)) EXEC(do_operator_spacing()) ECHO; BEGIN(divop); set_ident_norm(true); }
+{OPERATOR}                          { dealias_clear_mutated(false); previous_group = ASI_OTHER; dealias_prefix_reset(); EXEC(do_operator_spacing()) ECHO; BEGIN(divop); set_ident_norm(true);}
+{LITERAL}                           { dealias_clear_mutated(false); dealias_append(); EXEC(do_semicolon_insertion(ASI_GROUP_7)) EXEC(do_spacing(LITERAL)) ECHO; BEGIN(divop); set_ident_norm(true); }
+{IDENTIFIER}                        { 
+                                        if (unescape(YYText())) {
+                                            bool id_part = (token == DOT);
+                                            bool assignment_start = token == KEYWORD_VAR_DECL ||
+                                                                    token == PUNCTUATOR ||
+                                                                    token == UNDEFINED;
+                                            EXEC(do_semicolon_insertion(ASI_GROUP_7)) 
+                                            EXEC(do_spacing(IDENTIFIER))
+                                            EXEC(do_identifier_substitution(YYText(), id_part))
+                                            dealias_identifier(id_part, assignment_start);
+                                        }
+                                        else
+                                            EXEC(do_semicolon_insertion(ASI_GROUP_7)) 
+                                        BEGIN(divop);
+                                    }
 
 .|{ALL_UNICODE}                     { previous_group = ASI_OTHER; ECHO; token = UNDEFINED; BEGIN(INITIAL); set_ident_norm(true); }
 <<EOF>>                             { EEOF(eval_eof()) }
@@ -1390,10 +1404,21 @@ JSTokenizer::JSRet JSTokenizer::do_identifier_substitution(const char* lexeme, b
     if (ident_ctx.built_in(lexeme) && !id_part)
     {
         set_ident_norm(false);
-        return do_identifier_substitution(lexeme, true);
+        yyout << lexeme;
+        return EOS;
     }
 
-    const char* ident = ident_ctx.substitute(lexeme);
+    const char *ident = nullptr;
+    if (!id_part)
+        ident = ident_ctx.alias_lookup(lexeme);
+    if (ident)
+    {
+        set_ident_norm(false);
+        last_dealiased = std::string(YYText());
+        dealias_stored = true;
+    }
+    else
+        ident = ident_ctx.substitute(lexeme);
 
     if (!ident)
     {
@@ -1407,6 +1432,7 @@ JSTokenizer::JSRet JSTokenizer::do_identifier_substitution(const char* lexeme, b
         "'%s' => '%s'\n", lexeme, ident);
 
     yyout << ident;
+
     return EOS;
 }
 
@@ -1418,9 +1444,11 @@ JSTokenizer::JSRet JSTokenizer::do_semicolon_insertion(ASIGroup current)
         newline_found = false;
         if (insert_semicolon[previous_group][current])
         {
+            dealias_clear_mutated(false);
+            dealias_finalize();
             yyout << ';';
 
-            previous_group = ASI_OTHER;
+            previous_group = current;
             token = PUNCTUATOR;
             JSRet ret = EOS;
 
@@ -1429,7 +1457,6 @@ JSTokenizer::JSRet JSTokenizer::do_semicolon_insertion(ASIGroup current)
                 ret = p_scope_pop(meta_type());
                 set_meta_type(ScopeMetaType::NOT_SET);
             }
-
             return ret;
         }
     }
@@ -1648,6 +1675,10 @@ void JSTokenizer::states_adjust()
     case KEYWORD_FUNCTION: set_meta_type(ScopeMetaType::NOT_SET); break;
     case KEYWORD_BLOCK: p_scope_pop(meta_type()); set_meta_type(ScopeMetaType::NOT_SET); break;
     case KEYWORD_CLASS: set_meta_type(ScopeMetaType::NOT_SET); break;
+    case OPERATOR_ASSIGNMENT: alias_state = ALIAS_NONE; break;
+    case IDENTIFIER:
+        if (alias_state == ALIAS_DEFINITION) alias_state = ALIAS_NONE;
+        break;
     default: break;
     }
 
@@ -1832,3 +1863,115 @@ bool JSTokenizer::is_operator(JSToken tok)
     }
 }
 
+void JSTokenizer::dealias_clear_mutated(bool id_continue)
+{
+    if (!id_continue && prefix_increment && dealias_stored)
+    {
+        ident_ctx.add_alias(last_dealiased.c_str(), std::string(ident_ctx.substitute(last_dealiased.c_str())));
+    }
+    dealias_stored = false;
+    prefix_increment = false;
+}
+
+void JSTokenizer::dealias_increment()
+{
+    if (dealias_stored)
+    {
+        ident_ctx.add_alias(last_dealiased.c_str(), std::string(ident_ctx.substitute(last_dealiased.c_str())));
+    }
+    prefix_increment = token != IDENTIFIER && token != CLOSING_BRACKET;
+    dealias_stored = false;
+}
+
+void JSTokenizer::dealias_identifier(bool id_part, bool assignment_start)
+{
+    auto lexeme = YYText();
+    switch(alias_state)
+    {
+        case ALIAS_NONE:
+        {
+            if (assignment_start)
+            {
+                alias = std::string(YYText());
+                aliased.clear();
+                aliased.str("");
+                alias_state = ALIAS_DEFINITION;
+            }
+            break;
+        }
+        case ALIAS_PREFIX:
+        case ALIAS_DEFINITION:
+        {
+            dealias_reset();
+            break;
+        }
+        case ALIAS_EQUALS:
+            alias_state = ALIAS_VALUE;
+            //fallthrough
+        case ALIAS_VALUE:
+        {
+            auto dealias = ident_ctx.alias_lookup(lexeme);
+            if ((ident_ctx.built_in(lexeme) && !id_part) || (!ident_norm() && id_part))
+                aliased << YYText();
+            else if (dealias)
+                aliased << dealias;
+            else
+                dealias_reset();
+            break;
+        }
+    }
+}
+
+void JSTokenizer::dealias_equals(bool complex_assignment)
+{
+    if (alias_state == ALIAS_DEFINITION || alias_state == ALIAS_PREFIX)
+    {
+        if (complex_assignment)
+        {
+            if (ident_ctx.alias_lookup(alias.c_str()))
+                ident_ctx.add_alias(alias.c_str(), std::string(ident_ctx.substitute(alias.c_str())));
+            alias_state = ALIAS_NONE;
+        }
+        else
+            alias_state = ALIAS_EQUALS;
+    }
+}
+
+void JSTokenizer::dealias_reset()
+{
+    if (alias_state != ALIAS_NONE)
+    {
+        if (alias_state == ALIAS_VALUE || alias_state == ALIAS_EQUALS)
+            if (ident_ctx.alias_lookup(alias.c_str()))
+                ident_ctx.add_alias(alias.c_str(), std::string(ident_ctx.substitute(alias.c_str())));
+        alias_state = ALIAS_NONE;
+    }
+}
+
+void JSTokenizer::dealias_prefix_reset()
+{
+    if (alias_state == ALIAS_DEFINITION)
+        alias_state = ALIAS_PREFIX;
+    else
+        dealias_reset();
+}
+
+void JSTokenizer::dealias_append()
+{
+    if (alias_state == ALIAS_VALUE)
+        aliased << YYText();
+    else
+        dealias_reset();
+}
+
+void JSTokenizer::dealias_finalize()
+{
+    if (alias_state == ALIAS_VALUE)
+    {
+        ident_ctx.add_alias(alias.c_str(), aliased.str());
+        //FIXIT-E: add check for the 'sensitive' assignments here.
+        alias_state = ALIAS_NONE;
+    }
+    else
+        dealias_reset();
+}
\ No newline at end of file
index cff4cd399d4d51897b14970661290f116da69175..59efb5941c09e2743911ca415a18ae3dc0bf0015 100644 (file)
@@ -17,6 +17,17 @@ add_catch_test( js_normalizer_test
         ../js_normalizer.cc
         ../streambuf.cc
         ../util_cstring.cc
+        js_test_utils.cc
+)
+
+add_catch_test( js_dealias_test
+    SOURCES
+        ${FLEX_js_tokenizer_OUTPUTS}
+        ../js_identifier_ctx.cc
+        ../js_normalizer.cc
+        ../streambuf.cc
+        ../util_cstring.cc
+        js_test_utils.cc
 )
 
 add_catch_test( js_identifier_ctx_test
diff --git a/src/utils/test/js_dealias_test.cc b/src/utils/test/js_dealias_test.cc
new file mode 100644 (file)
index 0000000..574bad3
--- /dev/null
@@ -0,0 +1,810 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2021-2021 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+// js_dealias_test.cc author Oleksandr Serhiienko <oserhiie@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "catch/catch.hpp"
+
+#include "utils/test/js_test_utils.h"
+
+using namespace snort;
+
+// Unit tests
+
+#ifdef CATCH_TEST_BUILD
+
+TEST_CASE("De-aliasing - basic", "[JSNormalizer]")
+{
+    SECTION("function")
+        test_normalization(
+            "a = eval; a(\"2 + 2\");",
+            "var_0000=eval;eval(\"2 + 2\");"
+        );
+
+    SECTION("composite")
+        test_normalization(
+            "a = console.log.execute; a(\"2 + 2\");",
+            "var_0000=console.log.execute;console.log.execute(\"2 + 2\");"
+        );
+
+    SECTION("square bracket accessor")
+        test_normalization(
+            "a = console['log']; a(\"2 + 2\");",
+            "var_0000=console['log'];console['log'](\"2 + 2\");"
+        );
+
+    SECTION("function call")
+        test_normalization(
+            "a = eval('console.log(\\\'foo\\\')'); a;",
+            "var_0000=eval('console.log(\\\'foo\\\')');var_0000;"
+        );
+
+    SECTION("function call - composite")
+        test_normalization(
+            "a = console.log('123'); a;",
+            "var_0000=console.log('123');var_0000;"
+        );
+
+    SECTION("function call - square bracket accessor")
+        test_normalization(
+            "a = console['log']('foo'); a;",
+            "var_0000=console['log']('foo');var_0000;"
+        );
+
+    SECTION("function call - return value with dot accessor")
+        test_normalization(
+            "a = document.getElementById('id').field; a;",
+            "var_0000=document.getElementById('id').field;var_0000;"
+        );
+
+    SECTION("function call - return value with square bracket accessor")
+        test_normalization(
+            "a = document.getElementById('id')['field']; a;",
+            "var_0000=document.getElementById('id')['field'];var_0000;"
+        );
+
+    SECTION("with var keyword")
+        test_normalization(
+            "var a = eval; a('2 + 2');",
+            "var var_0000=eval;eval('2 + 2');"
+        );
+
+    SECTION("with let keyword")
+        test_normalization(
+            "let a = eval; a('2 + 2');",
+            "let var_0000=eval;eval('2 + 2');"
+        );
+
+    SECTION("with const keyword")
+        test_normalization(
+            "const a = eval; a('2 + 2');",
+            "const var_0000=eval;eval('2 + 2');"
+        );
+
+    SECTION("with *=")
+        test_normalization(
+            "a *= eval; a;",
+            "var_0000*=eval;var_0000;"
+        );
+
+    SECTION("with /=")
+        test_normalization(
+            "a /= eval; a;",
+            "var_0000/=eval;var_0000;"
+        );
+
+    SECTION("with %=")
+        test_normalization(
+            "a %= eval; a;",
+            "var_0000%=eval;var_0000;"
+        );
+
+    SECTION("with +=")
+        test_normalization(
+            "a += eval; a;",
+            "var_0000+=eval;var_0000;"
+        );
+
+    SECTION("with -=")
+        test_normalization(
+            "a -= eval; a;",
+            "var_0000-=eval;var_0000;"
+        );
+
+    SECTION("with <<=")
+        test_normalization(
+            "a <<= eval; a;",
+            "var_0000<<=eval;var_0000;"
+        );
+
+    SECTION("with >>=")
+        test_normalization(
+            "a >>= eval; a;",
+            "var_0000>>=eval;var_0000;"
+        );
+
+    SECTION("with >>>=")
+        test_normalization(
+            "a >>>= eval; a;",
+            "var_0000>>>=eval;var_0000;"
+        );
+
+    SECTION("with &=")
+        test_normalization(
+            "a &= eval; a;",
+            "var_0000&=eval;var_0000;"
+        );
+
+    SECTION("with ^=")
+        test_normalization(
+            "a ^= eval; a;",
+            "var_0000^=eval;var_0000;"
+        );
+
+    SECTION("with |=")
+        test_normalization(
+            "a |= eval; a;",
+            "var_0000|=eval;var_0000;"
+        );
+
+    SECTION("with prefix increment")
+        test_normalization(
+            "a = eval; a; ++a; a;",
+            "var_0000=eval;eval;++eval;var_0000;"
+        );
+
+    SECTION("with prefix decrement")
+        test_normalization(
+            "a = eval; a; --a; a;",
+            "var_0000=eval;eval;--eval;var_0000;"
+        );
+
+    SECTION("with postfix increment")
+        test_normalization(
+            "a = eval; a; a++; a;",
+            "var_0000=eval;eval;eval++;var_0000;"
+        );
+
+    SECTION("with postfix decrement")
+        test_normalization(
+            "a = eval; a; a--; a;",
+            "var_0000=eval;eval;eval--;var_0000;"
+        );
+
+    SECTION("with tilde")
+        test_normalization(
+            "a = eval; ~a; a;",
+            "var_0000=eval;~eval;eval;"
+        );
+
+    SECTION("with exclamation sign")
+        test_normalization(
+            "a = eval; !a; a;",
+            "var_0000=eval;!eval;eval;"
+        );
+
+    SECTION("with comparison operators")
+        test_normalization(
+            "a = eval;"
+            "a >= a;"
+            "a == a;"
+            "a != a;"
+            "a === a;"
+            "a !== a;"
+            "a < a;"
+            "a > a;"
+            "a <= a;",
+            "var_0000=eval;"
+            "eval>=eval;"
+            "eval==eval;"
+            "eval!=eval;"
+            "eval===eval;"
+            "eval!==eval;"
+            "eval<eval;"
+            "eval>eval;"
+            "eval<=eval;"
+        );
+
+    SECTION("with binary operators")
+        test_normalization(
+            "a = eval;"
+            "a & a;"
+            "a | a;"
+            "a ^ a;"
+            "a >> a;"
+            "a << a;",
+            "var_0000=eval;"
+            "eval&eval;"
+            "eval|eval;"
+            "eval^eval;"
+            "eval>>eval;"
+            "eval<<eval;"
+        );
+
+    SECTION("with logical operators")
+        test_normalization(
+            "a = eval;"
+            "a && a;"
+            "a || a;",
+            "var_0000=eval;"
+            "eval&&eval;"
+            "eval||eval;"
+        );
+
+    SECTION("with ternary operator")
+        test_normalization(
+            "a = eval; b = true ? a : a; a; b;"
+            "a = true ? a : a; a; b;",
+            "var_0000=eval;var_0001=true?eval:eval;eval;var_0001;"
+            "eval=true?var_0000:var_0000;var_0000;var_0001;"
+        );
+
+    SECTION("with single quotes string")
+        test_normalization(
+            "a = eval; a = 'str'; a;",
+            "var_0000=eval;eval='str';var_0000;"
+        );
+
+    SECTION("with double quotes string")
+        test_normalization(
+            "a = eval; a = \"str\"; a;",
+            "var_0000=eval;eval=\"str\";var_0000;"
+        );
+
+    SECTION("with regular expression")
+        test_normalization(
+            "a = eval; a = /regex/gs; a;",
+            "var_0000=eval;eval=/regex/gs;var_0000;"
+        );
+
+    SECTION("with keyword")
+        test_normalization(
+            "a = eval; delete a; a;",
+            "var_0000=eval;delete eval;eval;"
+        );
+
+    SECTION("within the parenthesis")
+        test_normalization(
+            "a = eval; (a);",
+            "var_0000=eval;(eval);"
+        );
+
+    SECTION("within the square brackets")
+        test_normalization(
+            "a = eval; b[a];",
+            "var_0000=eval;var_0001[eval];"
+        );
+
+    SECTION("redefinition")
+        test_normalization(
+            "a = eval; var a; let a; const a; a;",
+            "var_0000=eval;var eval;let eval;const eval;eval;"
+        );
+
+    SECTION("operand - lhs")
+        test_normalization(
+            "a = eval; a + 2; a;",
+            "var_0000=eval;eval+2;eval;"
+        );
+
+    SECTION("operand - rhs")
+        test_normalization(
+            "a = eval; 2 - a; a;",
+            "var_0000=eval;2-eval;eval;"
+        );
+
+    SECTION("assignment with modification")
+        test_normalization(
+            "var a = eval + b++; a;",
+            "var var_0000=eval+var_0001++;var_0000;"
+        );
+
+    SECTION("simple reassignment")
+        test_normalization(
+            "a = eval; a = 2; a;",
+            "var_0000=eval;eval=2;var_0000;"
+        );
+
+    SECTION("self reassignment")
+        test_normalization(
+            "a = eval; a = a; a;",
+            "var_0000=eval;eval=eval;eval;"
+        );
+
+    SECTION("self reassignment with modification")
+        test_normalization(
+            "a = eval; a += a; a;",
+            "var_0000=eval;eval+=var_0000;var_0000;"
+        );
+
+    SECTION("indirect reassignment")
+        test_normalization(
+            "a = eval; b = a; a = b; a();b();",
+            "var_0000=eval;var_0001=eval;eval=eval;eval();eval();"
+        );
+
+    SECTION("direct reassignment")
+        test_normalization(
+            "a = eval; a = console.log; a();",
+            "var_0000=eval;eval=console.log;console.log();"
+        );
+
+    SECTION("reassignment with modification")
+        test_normalization(
+            "a = eval; a += 2; a;",
+            "var_0000=eval;eval+=2;var_0000;"
+        );
+
+    SECTION("reassignment with operation")
+        test_normalization(
+            "a = eval; a = a % 2; a;",
+            "var_0000=eval;eval=eval%2;var_0000;"
+        );
+
+    SECTION("reassignment with modification and operation")
+        test_normalization(
+            "a = eval; a %= 2 * a; b = eval; b = 2 / a; a; b;",
+            "var_0000=eval;eval%=2*var_0000;var_0001=eval;eval=2/var_0000;var_0000;var_0001;"
+        );
+
+    SECTION("reassignment with prefix increment")
+        test_normalization(
+            "a = eval; a; b = ++a; a; b;",
+            "var_0000=eval;eval;var_0001=++eval;var_0000;var_0001;"
+        );
+
+    SECTION("reassignment with prefix decrement")
+        test_normalization(
+            "a = eval; a; b = --a; a; b;",
+            "var_0000=eval;eval;var_0001=--eval;var_0000;var_0001;"
+        );
+
+    SECTION("reassignment with postfix increment")
+        test_normalization(
+            "a = eval; a; b = a++; a; b;",
+            "var_0000=eval;eval;var_0001=eval++;var_0000;var_0001;"
+        );
+
+    SECTION("reassignment with postfix decrement")
+        test_normalization(
+            "a = eval; a; b = a--; a; b;",
+            "var_0000=eval;eval;var_0001=eval--;var_0000;var_0001;"
+        );
+
+    SECTION("reassignment with postfix decrement and operation")
+        test_normalization(
+            "a = eval; a; b = a-- + 2; a; b;",
+            "var_0000=eval;eval;var_0001=eval-- +2;var_0000;var_0001;"
+        );
+
+    SECTION("reassignment with postfix decrement and modification")
+        test_normalization(
+            "a = eval; a; b /= a--; a; b;",
+            "var_0000=eval;eval;var_0001/=eval--;var_0000;var_0001;"
+        );
+
+    SECTION("compound identifiers - dot accessor")
+        test_normalization(
+            "a = eval; foo.a; a; a.bar = 2; a;",
+            "var_0000=eval;var_0001.var_0000;eval;eval.bar=2;eval;"
+        );
+
+    SECTION("compound identifiers - square bracket accessor")
+        test_normalization(
+            "a = eval; foo['a']; a; a['bar'];",
+            "var_0000=eval;var_0001['a'];eval;eval['bar'];"
+        );
+
+    SECTION("multiple declaration")
+        test_normalization(
+            "var a, b = eval, c = eval; a; b; c;",
+            "var var_0000,var_0001=eval,var_0002=eval;var_0000;eval;eval;"
+        );
+
+    SECTION("with automatic semicolon insertion")
+        test_normalization(
+            "a \n = \n eval \n a \n eval;",
+            "var_0000=eval;eval;eval;"
+        );
+}
+
+TEST_CASE("De-aliasing - split", "[JSNormalizer]")
+{
+    SECTION("var keyword")
+        test_normalization({
+            {"v", "var_0000"},
+            {"ar a = eval; a;", "var var_0001=eval;eval;"}
+        });
+
+    SECTION("let keyword")
+        test_normalization({
+            {"l", "var_0000"},
+            {"et a = eval; a;", "let var_0001=eval;eval;"}
+        });
+
+    SECTION("const keyword")
+        test_normalization({
+            {"cons", "var_0000"},
+            {"t a = eval; a;", "const var_0001=eval;eval;"}
+        });
+
+    SECTION("alias name")
+        test_normalization({
+            {"var alias_", "var var_0000"},
+            {"name = eval; alias_name;", "var var_0001=eval;eval;"}
+        });
+
+    SECTION("fake alias name")
+        test_normalization({
+            {"a = eval; b = a", "var_0000=eval;var_0001=eval"},
+            {"b; b;", "var_0000=eval;var_0001=var_0002;var_0001;"}
+        });
+
+    SECTION("alias value")
+        test_normalization({
+            {"a = e", "var_0000=var_0001"},
+            {"val; a;", "var_0000=eval;var_0000;"}
+        });
+
+    SECTION("before assignment")
+        test_normalization({
+            {"a ", "var_0000"},
+            {"= eval; a;", "var_0000=eval;eval;"}
+        });
+
+    SECTION("after assignment")
+        test_normalization({
+            {"a =", "var_0000="},
+            {" eval; a;", "var_0000=eval;eval;"}
+        });
+
+    SECTION("assignment with modification")
+        test_normalization({
+            {"a *", "var_0000*"},
+            {"= eval; a;", "var_0000*=eval;var_0000;"}
+        });
+
+    SECTION("alias value as a function")
+        test_normalization({
+            {"a = e", "var_0000=var_0001"},
+            {"val; a;", "var_0000=eval;var_0000;"}
+        });
+
+    SECTION("composite alias value with dot accessor")
+        test_normalization({
+            {"a = console.", "var_0000=console."},
+            {"log; a();", "var_0000=console.log;console.log();"}
+        });
+
+    SECTION("composite alias value with square bracket accessor")
+        test_normalization({
+            {"a = console[", "var_0000=console["},
+            {"'log']; a();", "var_0000=console['log'];console['log']();"}
+        });
+
+    SECTION("function call")
+        test_normalization({
+            {"a = eval", "var_0000=eval"},
+            {"(); a;", "var_0000=eval();var_0000;"}
+        });
+
+    SECTION("function call - dot accessor")
+        test_normalization({
+            {"a = console.", "var_0000=console."},
+            {"log(); a;", "var_0000=console.log();var_0000;"}
+        });
+
+    SECTION("function call - square bracket accessor")
+        test_normalization({
+            {"a = console[", "var_0000=console["},
+            {"'log'](); a;", "var_0000=console['log']();var_0000;"}
+        });
+
+    SECTION("prefix increment")
+        test_normalization({
+            {"a = eval; +", "var_0000=eval;+"},
+            {"+a; a;", "var_0000=eval;++eval;var_0000;"}
+        });
+
+    SECTION("postfix increment")
+        test_normalization({
+            {"a = eval; a+", "var_0000=eval;eval+"},
+            {"+; a;", "var_0000=eval;eval++;var_0000;"}
+        });
+
+    SECTION("prefix decrement")
+        test_normalization({
+            {"a = eval; -", "var_0000=eval;-"},
+            {"-a; a;", "var_0000=eval;--eval;var_0000;"}
+        });
+
+    SECTION("postfix decrement")
+        test_normalization({
+            {"a = eval; a-", "var_0000=eval;eval-"},
+            {"-; a;", "var_0000=eval;eval--;var_0000;"}
+        });
+
+    SECTION("before operator")
+        test_normalization({
+            {"a = eval; a", "var_0000=eval;eval"},
+            {" + a; a;", "var_0000=eval;eval+eval;eval;"}
+        });
+
+    SECTION("after operator")
+        test_normalization({
+            {"a = eval; a +", "var_0000=eval;eval+"},
+            {" a; a;", "var_0000=eval;eval+eval;eval;"}
+        });
+
+    SECTION("comparison operator")
+        test_normalization({
+            {"a = eval; a =", "var_0000=eval;eval="},
+            {"= a; a;", "var_0000=eval;eval==eval;eval;"}
+        });
+
+    SECTION("logical operator")
+        test_normalization({
+            {"a = eval; a |", "var_0000=eval;eval|"},
+            {"| a; a;", "var_0000=eval;eval||eval;eval;"}
+        });
+
+    SECTION("before binary operator")
+        test_normalization({
+            {"a = eval; a ", "var_0000=eval;eval"},
+            {"| a; a;", "var_0000=eval;eval|eval;eval;"}
+        });
+
+    SECTION("after binary operator")
+        test_normalization({
+            {"a = eval; a |", "var_0000=eval;eval|"},
+            {" a; a;", "var_0000=eval;eval|eval;eval;"}
+        });
+
+    SECTION("shift operator")
+        test_normalization({
+            {"a = eval; a <", "var_0000=eval;eval<"},
+            {"< a; a;", "var_0000=eval;eval<<eval;eval;"}
+        });
+
+    SECTION("single quotes string")
+        test_normalization({
+            {"a = eval; a = ' ", "var_0000=eval;eval=' "},
+            {" '; a;", "var_0000=eval;eval='  ';var_0000;"}
+        });
+
+    SECTION("double quotes string")
+        test_normalization({
+            {"a = eval; a = \" ", "var_0000=eval;eval=\" "},
+            {" \"; a;", "var_0000=eval;eval=\"  \";var_0000;"}
+        });
+
+    SECTION("regex")
+        test_normalization({
+            {"a = eval; a = / ", "var_0000=eval;eval=/ "},
+            {" /g; a;", "var_0000=eval;eval=/  /g;var_0000;"}
+        });
+
+    SECTION("keyword")
+        test_normalization({
+            {"a = eval; type", "var_0000=eval;var_0001"},
+            {"of a; a;", "var_0000=eval;typeof eval;eval;"}
+        });
+
+    SECTION("assignment with modification")
+        test_normalization({
+            {"var a = eval", "var var_0000=eval"},
+            {" + b++; a;", "var var_0000=eval+var_0001++;var_0000;"}
+        });
+
+    SECTION("before reassignment")
+        test_normalization({
+            {"a = eval; a ", "var_0000=eval;eval"},
+            {" = b; a;", "var_0000=eval;eval=var_0001;var_0000;"}
+        });
+
+    SECTION("after reassignment")
+        test_normalization({
+            {"a = eval; a =", "var_0000=eval;eval="},
+            {" b; a;", "var_0000=eval;eval=var_0001;var_0000;"}
+        });
+
+    SECTION("before self reassignment")
+        test_normalization({
+            {"a = eval; a ", "var_0000=eval;eval"},
+            {" = a; a;", "var_0000=eval;eval=eval;eval;"}
+        });
+
+    SECTION("after self reassignment")
+        test_normalization({
+            {"a = eval; a =", "var_0000=eval;eval="},
+            {" a; a;", "var_0000=eval;eval=eval;eval;"}
+        });
+
+    SECTION("self reassignment with modification")
+        test_normalization({
+            {"a = eval; a +", "var_0000=eval;eval+"},
+            {"= a; a;", "var_0000=eval;eval+=var_0000;var_0000;"}
+        });
+
+    SECTION("reassignment with operation")
+        test_normalization({
+            {"a = eval; a = a +", "var_0000=eval;eval=eval+"},
+            {" 2; a;", "var_0000=eval;eval=eval+2;var_0000;"}
+        });
+
+    SECTION("automatic semicolon insertion")
+        test_normalization({
+            {"a \n = \n", "var_0000="},
+            {"eval \n a;", "var_0000=eval;eval;"}
+        });
+
+    SECTION("compound identifiers - dot accessor")
+        test_normalization({
+            {"a = eval; foo.", "var_0000=eval;var_0001."},
+            {"a; a; a.", "var_0000=eval;var_0001.var_0000;eval;eval."},
+            {"bar = 2; a;", "var_0000=eval;var_0001.var_0000;eval;eval.bar=2;eval;"}
+        });
+
+    SECTION("compound identifiers - square bracket accessor")
+        test_normalization({
+            {"a = eval; foo[", "var_0000=eval;var_0001["},
+            {"'a']; a; a[", "var_0000=eval;var_0001['a'];eval;eval["},
+            {"'bar']; a;", "var_0000=eval;var_0001['a'];eval;eval['bar'];eval;"}
+        });
+}
+
+TEST_CASE("De-aliasing - scopes", "[JSNormalizer]")
+{
+    SECTION("lookup through function")
+        test_normalization(
+            "a = eval; function f() { a; }",
+            "var_0000=eval;function var_0001(){eval;}"
+        );
+
+    SECTION("lookup through statement block")
+        test_normalization(
+            "a = eval; if (true) { a; }",
+            "var_0000=eval;if(true){eval;}"
+        );
+
+    SECTION("lookup through object")
+        test_normalization(
+            "a = eval; obj = {b : a}",
+            "var_0000=eval;var_0001={var_0002:eval}"
+        );
+
+    SECTION("lookup through code block")
+        test_normalization(
+            "a = eval; { a; }",
+            "var_0000=eval;{eval;}"
+        );
+
+    SECTION("assignment in function")
+        test_normalization(
+            "function f() { a = eval; a; } a;",
+            "function var_0000(){var_0001=eval;eval;}var_0001;"
+        );
+
+    SECTION("assignment in statement block")
+        test_normalization(
+            "if (true) { a = eval; a; } a;",
+            "if(true){var_0000=eval;eval;}var_0000;"
+        );
+
+    SECTION("assignment in object")
+        test_normalization(
+            "obj = { a : eval, b : a } a;",
+            "var_0000={var_0001:eval,var_0002:var_0001}var_0001;"
+        );
+
+    SECTION("assignment in code block")
+        test_normalization(
+            "{ a = eval; a; } a;",
+            "{var_0000=eval;eval;}var_0000;"
+        );
+
+    SECTION("reassignment in function")
+        test_normalization(
+            "a = eval; function f(){ a = console.log; a; a = 2; } a;",
+            "var_0000=eval;function var_0001(){eval=console.log;console.log;console.log=2;}eval;"
+        );
+
+    SECTION("reassignment in statement block")
+        test_normalization(
+            "a = eval; if (true) { a = console.log; a; a = 2; } a;",
+            "var_0000=eval;if(true){eval=console.log;console.log;console.log=2;}eval;"
+        );
+
+    SECTION("reassignment in object")
+        test_normalization(
+            "a = eval; obj = { a : console.log, b : a, a : 2 } a;",
+            "var_0000=eval;var_0001={eval:console.log,var_0002:eval,eval:2}eval;"
+        );
+
+    SECTION("reassignment in code block")
+        test_normalization(
+            "a = eval; { a = console.log; a; a = 2; } a;",
+            "var_0000=eval;{eval=console.log;console.log;console.log=2;}eval;"
+        );
+
+    SECTION("function argument")
+        test_normalization(
+            "a = eval; function f(a) { a; a = console.log; a; } f(a);",
+            "var_0000=eval;function var_0001(eval){eval;eval=console.log;console.log;}"
+            "var_0001(eval);"
+        );
+
+    SECTION("statement block argument")
+        test_normalization(
+            "a = eval; b = console.log; for ( let a, b; ; ) { a; b; } a; b;",
+            "var_0000=eval;var_0001=console.log;for(let eval,console.log;;){eval;console.log;}"
+            "eval;console.log;"
+        );
+
+    SECTION("single line statement block - lookup")
+        test_normalization(
+            "a = eval; if (true) a; a;",
+            "var_0000=eval;if(true)eval;eval;"
+        );
+
+    SECTION("single line statement block - reassignment")
+        test_normalization(
+            "a = eval; if (true) a = console.log; a;",
+            "var_0000=eval;if(true)eval=console.log;eval;"
+        );
+
+    SECTION("arrow function")
+        test_normalization(
+            "a = eval; b = (a) => {a; a = console.log; a;}; a;",
+            "var_0000=eval;var_0001=(eval)=>{eval;eval=console.log;console.log;};eval;"
+            // corner case
+        );
+
+    SECTION("default function argument")
+        test_normalization(
+            "a = eval; function f(a = 2) { a; } a;",
+            "var_0000=eval;function var_0001(eval=2){var_0000;}eval;"
+        );
+
+    SECTION("default arrow function argument")
+        test_normalization(
+            "a = eval; b = (a = 2) => { a; }; a;",
+            "var_0000=eval;var_0001=(eval=2)=>{var_0000;};var_0000;"
+            // corner case
+        );
+
+    SECTION("multiple nesting")
+        test_normalization(
+            "a = eval; function f() { a; a = console.log; a; "
+            "if (true) { a; a = document; a; } a; } a;",
+            "var_0000=eval;function var_0001(){eval;eval=console.log;console.log;"
+            "if(true){console.log;console.log=document;document;}console.log;}eval;"
+        );
+
+    SECTION("automatic semicolon insertion")
+        test_normalization(
+            "a = eval; if (true)\na\n=\nconsole\n.log\n\n a;",
+            "var_0000=eval;if(true)eval=console.log;eval;"
+        );
+}
+
+#endif // CATCH_TEST_BUILD
+
index 5e4e0661ed6c5a48e35b3222cc7c1567225088f1..7efffebe080e642c51d91f26c34647a53b1f2462 100644 (file)
 #include "config.h"
 #endif
 
-#include "catch/catch.hpp"
-
 #include <cstring>
-#include <tuple>
+
+#include "catch/catch.hpp"
 
 #include "utils/js_identifier_ctx.h"
 #include "utils/js_normalizer.h"
+#include "utils/test/js_test_utils.h"
 
-// Mock functions
-
-namespace snort
-{
-[[noreturn]] void FatalError(const char*, ...)
-{ exit(EXIT_FAILURE); }
-void trace_vprintf(const char*, TraceLevel, const char*, const Packet*, const char*, va_list) {}
-uint8_t TraceApi::get_constraints_generation() { return 0; }
-void TraceApi::filter(const Packet&) {}
-}
-
-THREAD_LOCAL const snort::Trace* http_trace = nullptr;
-
-class JSIdentifierCtxStub : public JSIdentifierCtxBase
-{
-public:
-    JSIdentifierCtxStub() = default;
-
-    const char* substitute(const char* identifier) override
-    { return identifier; }
-    bool built_in(const char*) const override
-    { return false; }
-    bool scope_push(JSProgramScopeType) override { return true; }
-    bool scope_pop(JSProgramScopeType) override { return true; }
-    void reset() override {}
-    size_t size() const override { return 0; }
-};
+using namespace snort;
 
-// Test cases
+// Unit tests
 
-using namespace snort;
+#ifdef CATCH_TEST_BUILD
 
 #define DEPTH 65535
 #define MAX_TEMPLATE_NESTING 4
 #define MAX_BRACKET_DEPTH 256
 #define MAX_SCOPE_DEPTH 256
-
-static const std::unordered_set<std::string> s_ident_built_in { "console", "eval", "document" };
-
-// Unit tests
-
-#ifdef CATCH_TEST_BUILD
-
 #define DST_SIZE 512
 
 #define NORMALIZE(src)                                             \
@@ -3709,16 +3676,6 @@ TEST_CASE("built-in identifiers split", "[JSNormalizer]")
     }
 }
 
-static void test_scope(const char* context, std::list<JSProgramScopeType> stack)
-{
-    std::string buf(context);
-    buf += "</script>";
-    JSIdentifierCtx ident_ctx(DEPTH, MAX_SCOPE_DEPTH, s_ident_built_in);
-    JSNormalizer normalizer(ident_ctx, DEPTH, MAX_TEMPLATE_NESTING, MAX_BRACKET_DEPTH);
-    normalizer.normalize(buf.c_str(), buf.size());
-    CHECK(ident_ctx.get_types() == stack);
-}
-
 TEST_CASE("Scope tracking - basic","[JSNormalizer]")
 {
     SECTION("Global only")
@@ -4011,24 +3968,6 @@ TEST_CASE("Scope tracking - closing","[JSNormalizer]")
         test_scope("function() { if (true)\nfor ( ; ; ) a = 2 }", {GLOBAL});
 }
 
-typedef std::tuple<const char*,const char*, std::list<JSProgramScopeType>> PduCase;
-static void test_normalization(std::list<PduCase> pdus)
-{
-    JSIdentifierCtx ident_ctx(DEPTH, MAX_SCOPE_DEPTH, s_ident_built_in);
-    JSNormalizer normalizer(ident_ctx, DEPTH, MAX_TEMPLATE_NESTING, MAX_BRACKET_DEPTH);
-    for(auto pdu:pdus)
-    {
-        const char* source;
-        const char* expected;
-        std::list<JSProgramScopeType> stack;
-        std::tie(source,expected,stack) = pdu;
-        normalizer.normalize(source, strlen(source));
-        std::string result_buf(normalizer.get_script(), normalizer.script_size());
-        CHECK(ident_ctx.get_types() == stack);
-        CHECK(result_buf == expected);
-    }
-}
-
 TEST_CASE("Scope tracking - over multiple PDU","[JSNormalizer]")
 {
     // Every line represents a PDU. Each pdu has input buffer, expected script
@@ -4186,17 +4125,6 @@ TEST_CASE("Scope tracking - over multiple PDU","[JSNormalizer]")
         });
 }
 
-static void test_normalization_bad(const char* source, const char* expected,
-    JSTokenizer::JSRet eret)
-{
-    JSIdentifierCtx ident_ctx(DEPTH, MAX_SCOPE_DEPTH, s_ident_built_in);
-    JSNormalizer normalizer(ident_ctx, DEPTH, MAX_TEMPLATE_NESTING, MAX_BRACKET_DEPTH);
-    auto ret = normalizer.normalize(source, strlen(source));
-    std::string result_buf(normalizer.get_script(), normalizer.script_size());
-    CHECK(eret == ret);
-    CHECK(result_buf == expected);
-}
-
 TEST_CASE("Scope tracking - error handling", "[JSNormalizer]")
 {
     SECTION("not identifier after var keyword")
diff --git a/src/utils/test/js_test_utils.cc b/src/utils/test/js_test_utils.cc
new file mode 100644 (file)
index 0000000..62914ff
--- /dev/null
@@ -0,0 +1,100 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2021-2021 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+// js_test_utils.cc author Danylo Kyrylov <dkyrylov@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "utils/test/js_test_utils.h"
+
+#include "catch/catch.hpp"
+
+namespace snort
+{
+[[noreturn]] void FatalError(const char*, ...)
+{ exit(EXIT_FAILURE); }
+void trace_vprintf(const char*, TraceLevel, const char*, const Packet*, const char*, va_list) {}
+uint8_t TraceApi::get_constraints_generation() { return 0; }
+void TraceApi::filter(const Packet&) {}
+}
+
+THREAD_LOCAL const snort::Trace* http_trace = nullptr;
+
+using namespace snort;
+
+void test_scope(const char* context, std::list<JSProgramScopeType> stack)
+{
+    std::string buf(context);
+    buf += "</script>";
+    JSIdentifierCtx ident_ctx(norm_depth, max_scope_depth, s_ident_built_in);
+    JSNormalizer normalizer(ident_ctx, norm_depth, max_template_nesting, max_bracket_depth);
+    normalizer.normalize(buf.c_str(), buf.size());
+    CHECK(ident_ctx.get_types() == stack);
+}
+
+void test_normalization(const char* source, const char* expected)
+{
+    JSIdentifierCtx ident_ctx(norm_depth, max_scope_depth, s_ident_built_in);
+    JSNormalizer normalizer(ident_ctx, norm_depth, max_template_nesting, max_bracket_depth);
+    normalizer.normalize(source, strlen(source));
+    std::string result_buf(normalizer.get_script(), normalizer.script_size());
+    CHECK(result_buf == expected);
+}
+
+void test_normalization_bad(const char* source, const char* expected, JSTokenizer::JSRet eret)
+{
+    JSIdentifierCtx ident_ctx(norm_depth, max_scope_depth, s_ident_built_in);
+    JSNormalizer normalizer(ident_ctx, norm_depth, max_template_nesting, max_bracket_depth);
+    auto ret = normalizer.normalize(source, strlen(source));
+    std::string result_buf(normalizer.get_script(), normalizer.script_size());
+    CHECK(eret == ret);
+    CHECK(result_buf == expected);
+}
+
+void test_normalization(const std::vector<PduCase>& pdus)
+{
+    JSIdentifierCtx ident_ctx(norm_depth, max_scope_depth, s_ident_built_in);
+    JSNormalizer normalizer(ident_ctx, norm_depth, max_template_nesting, max_bracket_depth);
+
+    for (const auto& pdu : pdus)
+    {
+        const char* source = pdu.first;
+        const char* expected = pdu.second;
+        normalizer.normalize(source, strlen(source));
+        std::string result_buf(normalizer.get_script(), normalizer.script_size());
+        CHECK(result_buf == expected);
+    }
+}
+
+void test_normalization(std::list<ScopedPduCase> pdus)
+{
+    JSIdentifierCtx ident_ctx(norm_depth, max_scope_depth, s_ident_built_in);
+    JSNormalizer normalizer(ident_ctx, norm_depth, max_template_nesting, max_bracket_depth);
+    for (auto pdu:pdus)
+    {
+        const char* source;
+        const char* expected;
+        std::list<JSProgramScopeType> stack;
+        std::tie(source,expected,stack) = pdu;
+        normalizer.normalize(source, strlen(source));
+        std::string result_buf(normalizer.get_script(), normalizer.script_size());
+        CHECK(ident_ctx.get_types() == stack);
+        CHECK(result_buf == expected);
+    }
+}
diff --git a/src/utils/test/js_test_utils.h b/src/utils/test/js_test_utils.h
new file mode 100644 (file)
index 0000000..dd4e2c8
--- /dev/null
@@ -0,0 +1,73 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2021-2021 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation.  You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+//--------------------------------------------------------------------------
+// js_test_utils.cc author Danylo Kyrylov <dkyrylov@cisco.com>
+
+#ifndef JS_TEST_UTILS_H
+#define JS_TEST_UTILS_H
+
+#include <list>
+#include <tuple>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "utils/js_identifier_ctx.h"
+#include "utils/js_normalizer.h"
+
+constexpr int norm_depth=65535;
+constexpr int max_template_nesting=4;
+constexpr int max_bracket_depth=256;
+constexpr int max_scope_depth=256;
+
+namespace snort
+{
+[[noreturn]] void FatalError(const char*, ...);
+void trace_vprintf(const char*, TraceLevel, const char*, const Packet*, const char*, va_list);
+}
+
+class JSIdentifierCtxStub : public JSIdentifierCtxBase
+{
+public:
+    JSIdentifierCtxStub() = default;
+
+    const char* substitute(const char* identifier) override
+    { return identifier; }
+    virtual void add_alias(const char*, const std::string&&) override {}
+    virtual const char* alias_lookup(const char* alias) const override
+    { return alias; }
+    bool built_in(const char*) const override
+    { return false; }
+    bool scope_push(JSProgramScopeType) override { return true; }
+    bool scope_pop(JSProgramScopeType) override { return true; }
+    void reset() override {}
+    size_t size() const override { return 0; }
+};
+
+static const std::unordered_set<std::string> s_ident_built_in { "console", "eval", "document" };
+
+void test_scope(const char* context, std::list<JSProgramScopeType> stack);
+void test_normalization(const char* source, const char* expected);
+void test_normalization_bad(const char* source, const char* expected, JSTokenizer::JSRet eret);
+typedef std::pair<const char*, const char*> PduCase;
+// source, expected for a single PDU
+void test_normalization(const std::vector<PduCase>& pdus);
+typedef std::tuple<const char*,const char*, std::list<JSProgramScopeType>> ScopedPduCase;
+// source, expected, and current scope type stack for a single PDU
+void test_normalization(std::list<ScopedPduCase> pdus);
+
+#endif // JS_TEST_UTILS_H