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;
#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())
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;
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;
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
// 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;
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
{
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();
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;
<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; }
<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} |
{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()) }
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)
{
"'%s' => '%s'\n", lexeme, ident);
yyout << ident;
+
return EOS;
}
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;
ret = p_scope_pop(meta_type());
set_meta_type(ScopeMetaType::NOT_SET);
}
-
return ret;
}
}
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;
}
}
}
+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
../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
--- /dev/null
+//--------------------------------------------------------------------------
+// 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
+
#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) \
}
}
-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")
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
});
}
-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")
--- /dev/null
+//--------------------------------------------------------------------------
+// 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);
+ }
+}
--- /dev/null
+//--------------------------------------------------------------------------
+// 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