Merge pull request #180 from xonotic/mem_leak_fix_on_failure_paths
[xonotic/gmqcc.git] / parser.cpp
index 7bb2a10f8f2401f783feebed27da3fb12fac980f..ddde65469ec77e7cb596e58451dc88096c5ad8f1 100644 (file)
@@ -139,7 +139,7 @@ static ast_expression* parser_find_local(parser_t *parser, const char *name, siz
     hash = util_hthash(parser->htglobals, name);
 
     *isparam = false;
-    for (i = vec_size(parser->variables); i > upto;) {
+    for (i = parser->variables.size(); i > upto;) {
         --i;
         if ( (e = (ast_expression*)util_htgeth(parser->variables[i], name, hash)) )
             return e;
@@ -171,7 +171,7 @@ static ast_value* parser_find_typedef(parser_t *parser, const char *name, size_t
     ast_value *e;
     hash = util_hthash(parser->typedefs[0], name);
 
-    for (i = vec_size(parser->typedefs); i > upto;) {
+    for (i = parser->typedefs.size(); i > upto;) {
         --i;
         if ( (e = (ast_value*)util_htgeth(parser->typedefs[i], name, hash)) )
             return e;
@@ -293,6 +293,23 @@ static bool rotate_entfield_array_index_nodes(ast_expression **out)
     return true;
 }
 
+static int store_op_for(ast_expression* expr)
+{
+    if (OPTS_FLAG(ADJUST_VECTOR_FIELDS) && expr->m_vtype == TYPE_FIELD && expr->m_next->m_vtype == TYPE_VECTOR) {
+        return type_storep_instr[TYPE_VECTOR];
+    }
+
+    if (ast_istype(expr, ast_member) && ast_istype(((ast_member*)expr)->m_owner, ast_entfield)) {
+        return type_storep_instr[expr->m_vtype];
+    }
+
+    if (ast_istype(expr, ast_entfield)) {
+        return type_storep_instr[expr->m_vtype];
+    }
+
+    return type_store_instr[expr->m_vtype];
+}
+
 static bool check_write_to(lex_ctx_t ctx, ast_expression *expr)
 {
     if (ast_istype(expr, ast_value)) {
@@ -728,6 +745,7 @@ static bool parser_sy_apply_operator(parser_t *parser, shunt *sy)
 
         case opid2('|','|'):
             generated_op += 1; /* INSTR_OR */
+            [[fallthrough]];
         case opid2('&','&'):
             generated_op += INSTR_AND;
             if (!(out = parser->m_fold.op(op, exprs))) {
@@ -857,10 +875,13 @@ static bool parser_sy_apply_operator(parser_t *parser, shunt *sy)
 
         case opid1('>'):
             generated_op += 1; /* INSTR_GT */
+            [[fallthrough]];
         case opid1('<'):
             generated_op += 1; /* INSTR_LT */
+            [[fallthrough]];
         case opid2('>', '='):
             generated_op += 1; /* INSTR_GE */
+            [[fallthrough]];
         case opid2('<', '='):
             generated_op += INSTR_LE;
             if (NotSameType(TYPE_FLOAT)) {
@@ -896,14 +917,7 @@ static bool parser_sy_apply_operator(parser_t *parser, shunt *sy)
         case opid1('='):
             if (ast_istype(exprs[0], ast_entfield)) {
                 ast_expression *field = ((ast_entfield*)exprs[0])->m_field;
-                if (OPTS_FLAG(ADJUST_VECTOR_FIELDS) &&
-                    exprs[0]->m_vtype == TYPE_FIELD &&
-                    exprs[0]->m_next->m_vtype == TYPE_VECTOR)
-                {
-                    assignop = type_storep_instr[TYPE_VECTOR];
-                }
-                else
-                    assignop = type_storep_instr[exprs[0]->m_vtype];
+                assignop = store_op_for(exprs[0]);
                 if (assignop == VINSTR_END || !field->m_next->compareType(*exprs[1]))
                 {
                     ast_type_to_string(field->m_next, ty1, sizeof(ty1));
@@ -921,15 +935,7 @@ static bool parser_sy_apply_operator(parser_t *parser, shunt *sy)
             }
             else
             {
-                if (OPTS_FLAG(ADJUST_VECTOR_FIELDS) &&
-                    exprs[0]->m_vtype == TYPE_FIELD &&
-                    exprs[0]->m_next->m_vtype == TYPE_VECTOR)
-                {
-                    assignop = type_store_instr[TYPE_VECTOR];
-                }
-                else {
-                    assignop = type_store_instr[exprs[0]->m_vtype];
-                }
+                assignop = store_op_for(exprs[0]);
 
                 if (assignop == VINSTR_END) {
                     ast_type_to_string(exprs[0], ty1, sizeof(ty1));
@@ -1024,10 +1030,7 @@ static bool parser_sy_apply_operator(parser_t *parser, shunt *sy)
                 return false;
             }
             (void)check_write_to(ctx, exprs[0]);
-            if (ast_istype(exprs[0], ast_entfield))
-                assignop = type_storep_instr[exprs[0]->m_vtype];
-            else
-                assignop = type_store_instr[exprs[0]->m_vtype];
+            assignop = store_op_for(exprs[0]);
             switch (exprs[0]->m_vtype) {
                 case TYPE_FLOAT:
                     out = new ast_binstore(ctx, assignop,
@@ -1059,10 +1062,7 @@ static bool parser_sy_apply_operator(parser_t *parser, shunt *sy)
                 return false;
             }
             (void)check_write_to(ctx, exprs[0]);
-            if (ast_istype(exprs[0], ast_entfield))
-                assignop = type_storep_instr[exprs[0]->m_vtype];
-            else
-                assignop = type_store_instr[exprs[0]->m_vtype];
+            assignop = store_op_for(exprs[0]);
             switch (exprs[0]->m_vtype) {
                 case TYPE_FLOAT:
                     out = new ast_binstore(ctx, assignop,
@@ -1103,10 +1103,7 @@ static bool parser_sy_apply_operator(parser_t *parser, shunt *sy)
                 return false;
             }
             (void)check_write_to(ctx, exprs[0]);
-            if (ast_istype(exprs[0], ast_entfield))
-                assignop = type_storep_instr[exprs[0]->m_vtype];
-            else
-                assignop = type_store_instr[exprs[0]->m_vtype];
+            assignop = store_op_for(exprs[0]);
             if (exprs[0]->m_vtype == TYPE_FLOAT)
                 out = new ast_binstore(ctx, assignop,
                                        (op->id == opid2('^','=') ? VINSTR_BITXOR : op->id == opid2('&','=') ? INSTR_BITAND : INSTR_BITOR),
@@ -1128,10 +1125,7 @@ static bool parser_sy_apply_operator(parser_t *parser, shunt *sy)
                               ty1, ty2);
                 return false;
             }
-            if (ast_istype(exprs[0], ast_entfield))
-                assignop = type_storep_instr[exprs[0]->m_vtype];
-            else
-                assignop = type_store_instr[exprs[0]->m_vtype];
+            assignop = store_op_for(exprs[0]);
             if (exprs[0]->m_vtype == TYPE_FLOAT)
                 out = fold::binary(ctx, INSTR_BITAND, exprs[0], exprs[1]);
             else
@@ -1263,14 +1257,14 @@ static bool parser_close_call(parser_t *parser, shunt *sy)
      * intrinsic call and just evaluate it i.e constant fold it.
      */
     if (fold && ast_istype(fun, ast_value) && ((ast_value*)fun)->m_intrinsic) {
-        ast_expression **exprs  = nullptr;
+        std::vector<ast_expression*> exprs;
         ast_expression *foldval = nullptr;
 
+        exprs.reserve(paramcount);
         for (i = 0; i < paramcount; i++)
-            vec_push(exprs, sy->out[fid+1 + i].out);
+            exprs.push_back(sy->out[fid+1 + i].out);
 
-        if (!(foldval = parser->m_intrin.do_fold((ast_value*)fun, exprs))) {
-            vec_free(exprs);
+        if (!(foldval = parser->m_intrin.do_fold((ast_value*)fun, exprs.data()))) {
             goto fold_leave;
         }
 
@@ -1280,7 +1274,6 @@ static bool parser_close_call(parser_t *parser, shunt *sy)
          */
         sy->out[fid] = syexp(foldval->m_context, foldval);
         sy->out.erase(sy->out.end() - paramcount, sy->out.end());
-        vec_free(exprs);
 
         return true;
     }
@@ -1668,13 +1661,16 @@ static bool parse_sya_operand(parser_t *parser, shunt *sy, bool with_labels)
         }
         else
         {
-            if (ast_istype(var, ast_value)) {
-                ((ast_value*)var)->m_uses++;
+            // promote these to norefs
+            if (ast_istype(var, ast_value))
+            {
+                ((ast_value *)var)->m_flags |= AST_FLAG_NOREF;
             }
-            else if (ast_istype(var, ast_member)) {
-                ast_member *mem = (ast_member*)var;
+            else if (ast_istype(var, ast_member))
+            {
+                ast_member *mem = (ast_member *)var;
                 if (ast_istype(mem->m_owner, ast_value))
-                    ((ast_value*)(mem->m_owner))->m_uses++;
+                    ((ast_value *)mem->m_owner)->m_flags |= AST_FLAG_NOREF;
             }
         }
         sy->out.push_back(syexp(parser_ctx(parser), var));
@@ -1993,11 +1989,11 @@ static ast_expression* parse_expression(parser_t *parser, bool stopatcomma, bool
 
 static void parser_enterblock(parser_t *parser)
 {
-    vec_push(parser->variables, util_htnew(PARSER_HT_SIZE));
-    vec_push(parser->_blocklocals, vec_size(parser->_locals));
-    vec_push(parser->typedefs, util_htnew(TYPEDEF_HT_SIZE));
-    vec_push(parser->_blocktypedefs, vec_size(parser->_typedefs));
-    vec_push(parser->_block_ctx, parser_ctx(parser));
+    parser->variables.push_back(util_htnew(PARSER_HT_SIZE));
+    parser->_blocklocals.push_back(parser->_locals.size());
+    parser->typedefs.push_back(util_htnew(TYPEDEF_HT_SIZE));
+    parser->_blocktypedefs.push_back(parser->_typedefs.size());
+    parser->_block_ctx.push_back(parser_ctx(parser));
 }
 
 static bool parser_leaveblock(parser_t *parser)
@@ -2005,48 +2001,37 @@ static bool parser_leaveblock(parser_t *parser)
     bool   rv = true;
     size_t locals, typedefs;
 
-    if (vec_size(parser->variables) <= PARSER_HT_LOCALS) {
+    if (parser->variables.size() <= PARSER_HT_LOCALS) {
         parseerror(parser, "internal error: parser_leaveblock with no block");
         return false;
     }
 
-    util_htdel(vec_last(parser->variables));
+    util_htdel(parser->variables.back());
 
-    vec_pop(parser->variables);
-    if (!vec_size(parser->_blocklocals)) {
+    parser->variables.pop_back();
+    if (!parser->_blocklocals.size()) {
         parseerror(parser, "internal error: parser_leaveblock with no block (2)");
         return false;
     }
 
-    locals = vec_last(parser->_blocklocals);
-    vec_pop(parser->_blocklocals);
-    while (vec_size(parser->_locals) != locals) {
-        ast_expression *e = vec_last(parser->_locals);
-        ast_value      *v = (ast_value*)e;
-        vec_pop(parser->_locals);
-        if (ast_istype(e, ast_value) && !v->m_uses) {
-            if (compile_warning(v->m_context, WARN_UNUSED_VARIABLE, "unused variable: `%s`", v->m_name))
-                rv = false;
-        }
-    }
+    locals = parser->_blocklocals.back();
+    parser->_blocklocals.pop_back();
+    parser->_locals.resize(locals);
 
-    typedefs = vec_last(parser->_blocktypedefs);
-    while (vec_size(parser->_typedefs) != typedefs) {
-        delete vec_last(parser->_typedefs);
-        vec_pop(parser->_typedefs);
-    }
-    util_htdel(vec_last(parser->typedefs));
-    vec_pop(parser->typedefs);
+    typedefs = parser->_blocktypedefs.back();
+    parser->_typedefs.resize(typedefs);
+    util_htdel(parser->typedefs.back());
+    parser->typedefs.pop_back();
 
-    vec_pop(parser->_block_ctx);
+    parser->_block_ctx.pop_back();
 
     return rv;
 }
 
 static void parser_addlocal(parser_t *parser, const char *name, ast_expression *e)
 {
-    vec_push(parser->_locals, e);
-    util_htset(vec_last(parser->variables), name, (void*)e);
+    parser->_locals.push_back(e);
+    util_htset(parser->variables.back(), name, (void*)e);
 }
 static void parser_addlocal(parser_t *parser, const std::string &name, ast_expression *e) {
     return parser_addlocal(parser, name.c_str(), e);
@@ -2634,6 +2619,7 @@ static bool parse_return(parser_t *parser, ast_block *block, ast_expression **ou
             retval = new ast_value(ctx, "#LOCAL_RETURN", TYPE_VOID);
             retval->adoptType(*expected->m_next);
             parser->function->m_return_value = retval;
+            parser->function->m_return_value->m_flags |= AST_FLAG_NOREF;
         }
 
         if (!exp->compareType(*retval)) {
@@ -3120,6 +3106,52 @@ static bool parse_switch_go(parser_t *parser, ast_block *block, ast_expression *
                 return false;
             }
             swcase.m_value = parse_expression_leave(parser, false, false, false);
+
+            if (!operand->compareType(*swcase.m_value)) {
+                char ty1[1024];
+                char ty2[1024];
+
+                ast_type_to_string(swcase.m_value, ty1, sizeof ty1);
+                ast_type_to_string(operand, ty2, sizeof ty2);
+
+                auto fnLiteral = [](ast_expression *expression) -> char* {
+                    if (!ast_istype(expression, ast_value))
+                        return nullptr;
+                    ast_value *value = (ast_value *)expression;
+                    if (!value->m_hasvalue)
+                        return nullptr;
+                    char *string = nullptr;
+                    basic_value_t *constval = &value->m_constval;
+                    switch (value->m_vtype)
+                    {
+                    case TYPE_FLOAT:
+                        util_asprintf(&string, "%.2f", constval->vfloat);
+                        return string;
+                    case TYPE_VECTOR:
+                        util_asprintf(&string, "'%.2f %.2f %.2f'",
+                            constval->vvec.x,
+                            constval->vvec.y,
+                            constval->vvec.z);
+                        return string;
+                    case TYPE_STRING:
+                        util_asprintf(&string, "\"%s\"", constval->vstring);
+                        return string;
+                    default:
+                        break;
+                    }
+                    return nullptr;
+                };
+
+                char *literal = fnLiteral(swcase.m_value);
+                if (literal)
+                    compile_error(parser_ctx(parser), "incompatible type `%s` for switch case `%s` expected `%s`", ty1, literal, ty2);
+                else
+                    compile_error(parser_ctx(parser), "incompatible type `%s` for switch case expected `%s`", ty1, ty2);
+                mem_d(literal);
+                delete switchnode;
+                return false;
+            }
+
             if (!swcase.m_value) {
                 delete switchnode;
                 parseerror(parser, "expected expression for case");
@@ -3581,9 +3613,9 @@ static bool parse_enum(parser_t *parser)
     bool        flag = false;
     bool        reverse = false;
     qcfloat_t     num = 0;
-    ast_value **values = nullptr;
     ast_value  *var = nullptr;
     ast_value  *asvalue;
+    std::vector<ast_value*> values;
 
     ast_expression *old;
 
@@ -3625,7 +3657,7 @@ static bool parse_enum(parser_t *parser)
                 break;
             }
             parseerror(parser, "expected identifier or `}`");
-            goto onerror;
+            return false;
         }
 
         old = parser_find_field(parser, parser_tokval(parser));
@@ -3634,11 +3666,11 @@ static bool parse_enum(parser_t *parser)
         if (old) {
             parseerror(parser, "value `%s` has already been declared here: %s:%i",
                        parser_tokval(parser), old->m_context.file, old->m_context.line);
-            goto onerror;
+            return false;
         }
 
         var = new ast_value(parser_ctx(parser), parser_tokval(parser), TYPE_FLOAT);
-        vec_push(values, var);
+        values.push_back(var);
         var->m_cvq             = CV_CONST;
         var->m_hasvalue        = true;
 
@@ -3648,7 +3680,7 @@ static bool parse_enum(parser_t *parser)
 
         if (!parser_next(parser)) {
             parseerror(parser, "expected `=`, `}` or comma after identifier");
-            goto onerror;
+            return false;
         }
 
         if (parser->tok == ',')
@@ -3657,12 +3689,12 @@ static bool parse_enum(parser_t *parser)
             break;
         if (parser->tok != '=') {
             parseerror(parser, "expected `=`, `}` or comma after identifier");
-            goto onerror;
+            return false;
         }
 
         if (!parser_next(parser)) {
             parseerror(parser, "expected expression after `=`");
-            goto onerror;
+            return false;
         }
 
         /* We got a value! */
@@ -3670,7 +3702,7 @@ static bool parse_enum(parser_t *parser)
         asvalue = (ast_value*)old;
         if (!ast_istype(old, ast_value) || asvalue->m_cvq != CV_CONST || !asvalue->m_hasvalue) {
             compile_error(var->m_context, "constant value or expression expected");
-            goto onerror;
+            return false;
         }
         num = (var->m_constval.vfloat = asvalue->m_constval.vfloat) + 1;
 
@@ -3678,38 +3710,33 @@ static bool parse_enum(parser_t *parser)
             break;
         if (parser->tok != ',') {
             parseerror(parser, "expected `}` or comma after expression");
-            goto onerror;
+            return false;
         }
     }
 
     /* patch them all (for reversed attribute) */
     if (reverse) {
         size_t i;
-        for (i = 0; i < vec_size(values); i++)
-            values[i]->m_constval.vfloat = vec_size(values) - i - 1;
+        for (i = 0; i < values.size(); i++)
+            values[i]->m_constval.vfloat = values.size() - i - 1;
     }
 
     if (parser->tok != '}') {
         parseerror(parser, "internal error: breaking without `}`");
-        goto onerror;
+        return false;
     }
 
     if (!parser_next(parser) || parser->tok != ';') {
         parseerror(parser, "expected semicolon after enumeration");
-        goto onerror;
+        return false;
     }
 
     if (!parser_next(parser)) {
         parseerror(parser, "parse error after enumeration");
-        goto onerror;
+        return false;
     }
 
-    vec_free(values);
     return true;
-
-onerror:
-    vec_free(values);
-    return false;
 }
 
 static bool parse_block_into(parser_t *parser, ast_block *block)
@@ -4128,7 +4155,7 @@ static bool parse_function_body(parser_t *parser, ast_value *var)
     parser->function = old;
     if (!parser_leaveblock(parser))
         retval = false;
-    if (vec_size(parser->variables) != PARSER_HT_LOCALS) {
+    if (parser->variables.size() != PARSER_HT_LOCALS) {
         parseerror(parser, "internal error: local scopes left");
         retval = false;
     }
@@ -4947,15 +4974,15 @@ static bool parse_typedef(parser_t *parser)
         return false;
     }
 
-    if ( (oldtype = parser_find_typedef(parser, typevar->m_name, vec_last(parser->_blocktypedefs))) ) {
+    if ( (oldtype = parser_find_typedef(parser, typevar->m_name, parser->_blocktypedefs.back())) ) {
         parseerror(parser, "type `%s` has already been declared here: %s:%i",
                    typevar->m_name, oldtype->m_context.file, oldtype->m_context.line);
         delete typevar;
         return false;
     }
 
-    vec_push(parser->_typedefs, typevar);
-    util_htset(vec_last(parser->typedefs), typevar->m_name.c_str(), typevar);
+    parser->_typedefs.emplace_back(typevar);
+    util_htset(parser->typedefs.back(), typevar->m_name.c_str(), typevar);
 
     if (parser->tok != ';') {
         parseerror(parser, "expected semicolon after typedef");
@@ -5187,12 +5214,12 @@ static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofield
             /* Deal with end_sys_ vars */
             was_end = false;
             if (var->m_name == "end_sys_globals") {
-                var->m_uses++;
+                var->m_flags |= AST_FLAG_NOREF;
                 parser->crc_globals = parser->globals.size();
                 was_end = true;
             }
             else if (var->m_name == "end_sys_fields") {
-                var->m_uses++;
+                var->m_flags |= AST_FLAG_NOREF;
                 parser->crc_fields = parser->fields.size();
                 was_end = true;
             }
@@ -5322,7 +5349,7 @@ static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofield
         }
         else /* it's not a global */
         {
-            old = parser_find_local(parser, var->m_name, vec_size(parser->variables)-1, &isparam);
+            old = parser_find_local(parser, var->m_name, parser->variables.size()-1, &isparam);
             if (old && !isparam) {
                 parseerror(parser, "local `%s` already declared here: %s:%i",
                            var->m_name, old->m_context.file, (int)old->m_context.line);
@@ -5352,9 +5379,8 @@ static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofield
             }
         }
 
-        /* in a noref section we simply bump the usecount */
         if (noref || parser->noref)
-            var->m_uses++;
+            var->m_flags |= AST_FLAG_NOREF;
 
         /* Part 2:
          * Create the global/local, and deal with vector types.
@@ -5450,7 +5476,7 @@ static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofield
                     prefix_len = defname.length();
 
                     // Add it to the local scope
-                    util_htset(vec_last(parser->variables), var->m_name.c_str(), (void*)var);
+                    util_htset(parser->variables.back(), var->m_name.c_str(), (void*)var);
 
                     // now rename the global
                     defname.append(var->m_name);
@@ -5480,8 +5506,8 @@ static bool parse_variable(parser_t *parser, ast_block *localblock, bool nofield
                     if (isvector) {
                         defname.erase(prefix_len);
                         for (i = 0; i < 3; ++i) {
-                            util_htset(vec_last(parser->variables), me[i]->m_name.c_str(), (void*)(me[i]));
-                            me[i]->m_name = move(defname + me[i]->m_name);
+                            util_htset(parser->variables.back(), me[i]->m_name.c_str(), (void*)(me[i]));
+                            me[i]->m_name = defname + me[i]->m_name;
                             parser->globals.push_back(me[i]);
                         }
                     }
@@ -5718,15 +5744,15 @@ skipvar:
                 /* remove it from the current locals */
                 if (isvector) {
                     for (i = 0; i < 3; ++i) {
-                        vec_pop(parser->_locals);
+                        parser->_locals.pop_back();
                         localblock->m_collect.pop_back();
                     }
                 }
                 /* do sanity checking, this function really needs refactoring */
-                if (vec_last(parser->_locals) != var)
+                if (parser->_locals.back() != var)
                     parseerror(parser, "internal error: unexpected change in local variable handling");
                 else
-                    vec_pop(parser->_locals);
+                    parser->_locals.pop_back();
                 if (localblock->m_locals.back() != var)
                     parseerror(parser, "internal error: unexpected change in local variable handling (2)");
                 else
@@ -5755,7 +5781,10 @@ skipvar:
                         var->m_cvq = CV_CONST;
                     }
                     if (cval == parser->nil)
+                    {
                         var->m_flags |= AST_FLAG_INITIALIZED;
+                        var->m_flags |= AST_FLAG_NOREF;
+                    }
                     else
                     {
                         var->m_hasvalue = true;
@@ -5977,21 +6006,72 @@ static void generate_checksum(parser_t *parser, ir_builder *ir)
     ir->m_code->crc = crc;
 }
 
+parser_t::parser_t()
+    : lex(nullptr)
+    , tok(0)
+    , ast_cleaned(false)
+    , translated(0)
+    , crc_globals(0)
+    , crc_fields(0)
+    , function(nullptr)
+    , aliases(util_htnew(PARSER_HT_SIZE))
+    , htfields(util_htnew(PARSER_HT_SIZE))
+    , htglobals(util_htnew(PARSER_HT_SIZE))
+    , assign_op(nullptr)
+    , noref(false)
+    , max_param_count(1)
+    // finish initializing the rest of the parser before initializing
+    // m_fold and m_intrin with the parser passed along
+    , m_fold()
+    , m_intrin()
+{
+    variables.push_back(htfields);
+    variables.push_back(htglobals);
+    typedefs.push_back(util_htnew(TYPEDEF_HT_SIZE));
+    _blocktypedefs.push_back(0);
+
+    lex_ctx_t empty_ctx;
+    empty_ctx.file   = "<internal>";
+    empty_ctx.line   = 0;
+    empty_ctx.column = 0;
+    nil = new ast_value(empty_ctx, "nil", TYPE_NIL);
+    nil->m_cvq = CV_CONST;
+    if (OPTS_FLAG(UNTYPED_NIL))
+        util_htset(htglobals, "nil", (void*)nil);
+
+    const_vec[0] = new ast_value(empty_ctx, "<vector.x>", TYPE_NOEXPR);
+    const_vec[1] = new ast_value(empty_ctx, "<vector.y>", TYPE_NOEXPR);
+    const_vec[2] = new ast_value(empty_ctx, "<vector.z>", TYPE_NOEXPR);
+
+    if (OPTS_OPTION_BOOL(OPTION_ADD_INFO)) {
+        reserved_version = new ast_value(empty_ctx, "reserved:version", TYPE_STRING);
+        reserved_version->m_cvq = CV_CONST;
+        reserved_version->m_hasvalue = true;
+        reserved_version->m_flags |= AST_FLAG_INCLUDE_DEF;
+        reserved_version->m_flags |= AST_FLAG_NOREF;
+        reserved_version->m_constval.vstring = util_strdup(GMQCC_FULL_VERSION_STRING);
+    } else {
+        reserved_version = nullptr;
+    }
+
+    m_fold = fold(this);
+    m_intrin = intrin(this);
+}
+
+parser_t::~parser_t()
+{
+    remove_ast();
+}
+
 parser_t *parser_create()
 {
     parser_t *parser;
-    lex_ctx_t empty_ctx;
     size_t i;
 
-    parser = (parser_t*)mem_a(sizeof(parser_t));
+    parser = new parser_t;
     if (!parser)
         return nullptr;
 
-    memset(parser, 0, sizeof(*parser));
-
-    // TODO: remove
-    new (parser) parser_t();
-
     for (i = 0; i < operator_count; ++i) {
         if (operators[i].id == opid1('=')) {
             parser->assign_op = operators+i;
@@ -6000,43 +6080,10 @@ parser_t *parser_create()
     }
     if (!parser->assign_op) {
         con_err("internal error: initializing parser: failed to find assign operator\n");
-        mem_d(parser);
+        delete parser;
         return nullptr;
     }
 
-    vec_push(parser->variables, parser->htfields  = util_htnew(PARSER_HT_SIZE));
-    vec_push(parser->variables, parser->htglobals = util_htnew(PARSER_HT_SIZE));
-    vec_push(parser->typedefs, util_htnew(TYPEDEF_HT_SIZE));
-    vec_push(parser->_blocktypedefs, 0);
-
-    parser->aliases = util_htnew(PARSER_HT_SIZE);
-
-    empty_ctx.file   = "<internal>";
-    empty_ctx.line   = 0;
-    empty_ctx.column = 0;
-    parser->nil = new ast_value(empty_ctx, "nil", TYPE_NIL);
-    parser->nil->m_cvq = CV_CONST;
-    if (OPTS_FLAG(UNTYPED_NIL))
-        util_htset(parser->htglobals, "nil", (void*)parser->nil);
-
-    parser->max_param_count = 1;
-
-    parser->const_vec[0] = new ast_value(empty_ctx, "<vector.x>", TYPE_NOEXPR);
-    parser->const_vec[1] = new ast_value(empty_ctx, "<vector.y>", TYPE_NOEXPR);
-    parser->const_vec[2] = new ast_value(empty_ctx, "<vector.z>", TYPE_NOEXPR);
-
-    if (OPTS_OPTION_BOOL(OPTION_ADD_INFO)) {
-        parser->reserved_version = new ast_value(empty_ctx, "reserved:version", TYPE_STRING);
-        parser->reserved_version->m_cvq = CV_CONST;
-        parser->reserved_version->m_hasvalue = true;
-        parser->reserved_version->m_flags |= AST_FLAG_INCLUDE_DEF;
-        parser->reserved_version->m_constval.vstring = util_strdup(GMQCC_FULL_VERSION_STRING);
-    } else {
-        parser->reserved_version = nullptr;
-    }
-
-    parser->m_fold = fold(parser);
-    parser->m_intrin = intrin(parser);
     return parser;
 }
 
@@ -6092,54 +6139,42 @@ bool parser_compile_string(parser_t *parser, const char *name, const char *str,
     return parser_compile(parser);
 }
 
-static void parser_remove_ast(parser_t *parser)
+void parser_t::remove_ast()
 {
-    size_t i;
-    if (parser->ast_cleaned)
+    if (ast_cleaned)
         return;
-    parser->ast_cleaned = true;
-    for (auto &it : parser->accessors) {
+    ast_cleaned = true;
+    for (auto &it : accessors) {
         delete it->m_constval.vfunc;
         it->m_constval.vfunc = nullptr;
         delete it;
     }
-    for (auto &it : parser->functions) delete it;
-    for (auto &it : parser->globals) delete it;
-    for (auto &it : parser->fields) delete it;
-
-    for (i = 0; i < vec_size(parser->variables); ++i)
-        util_htdel(parser->variables[i]);
-    vec_free(parser->variables);
-    vec_free(parser->_blocklocals);
-    vec_free(parser->_locals);
+    for (auto &it : functions) delete it;
+    for (auto &it : globals) delete it;
+    for (auto &it : fields) delete it;
 
-    for (i = 0; i < vec_size(parser->_typedefs); ++i)
-        delete parser->_typedefs[i];
-    vec_free(parser->_typedefs);
-    for (i = 0; i < vec_size(parser->typedefs); ++i)
-        util_htdel(parser->typedefs[i]);
-    vec_free(parser->typedefs);
-    vec_free(parser->_blocktypedefs);
+    for (auto &it : variables) util_htdel(it);
+    variables.clear();
+    _blocklocals.clear();
+    _locals.clear();
 
-    vec_free(parser->_block_ctx);
+    _typedefs.clear();
+    for (auto &it : typedefs) util_htdel(it);
+    typedefs.clear();
+    _blocktypedefs.clear();
 
-    delete parser->nil;
+    _block_ctx.clear();
 
-    delete parser->const_vec[0];
-    delete parser->const_vec[1];
-    delete parser->const_vec[2];
+    delete nil;
 
-    if (parser->reserved_version)
-        delete parser->reserved_version;
+    delete const_vec[0];
+    delete const_vec[1];
+    delete const_vec[2];
 
-    util_htdel(parser->aliases);
-}
+    if (reserved_version)
+        delete reserved_version;
 
-void parser_cleanup(parser_t *parser)
-{
-    parser_remove_ast(parser);
-    parser->~parser_t();
-    mem_d(parser);
+    util_htdel(aliases);
 }
 
 static bool parser_set_coverage_func(parser_t *parser, ir_builder *ir) {
@@ -6227,7 +6262,7 @@ bool parser_finish(parser_t *parser, const char *output)
         if (!ast_istype(it, ast_value))
             continue;
         asvalue = (ast_value*)it;
-        if (!asvalue->m_uses && !asvalue->m_hasvalue && asvalue->m_vtype != TYPE_FUNCTION) {
+        if (!(asvalue->m_flags & AST_FLAG_NOREF) && asvalue->m_cvq != CV_CONST && asvalue->m_vtype != TYPE_FUNCTION) {
             retval = retval && !compile_warning(asvalue->m_context, WARN_UNUSED_VARIABLE,
                                                 "unused global: `%s`", asvalue->m_name);
         }
@@ -6323,13 +6358,17 @@ bool parser_finish(parser_t *parser, const char *output)
             return false;
         }
     }
-    parser_remove_ast(parser);
+    parser->remove_ast();
 
-    if (compile_Werrors) {
-        con_out("*** there were warnings treated as errors\n");
-        compile_show_werrors();
-        retval = false;
-    }
+    auto fnCheckWErrors = [&retval]() {
+        if (compile_Werrors) {
+            con_out("*** there were warnings treated as errors\n");
+            compile_show_werrors();
+            retval = false;
+        }
+    };
+
+    fnCheckWErrors();
 
     if (retval) {
         if (OPTS_OPTION_BOOL(OPTION_DUMPFIN))
@@ -6340,6 +6379,9 @@ bool parser_finish(parser_t *parser, const char *output)
             delete ir;
             return false;
         }
+
+        // ir->generate can generate compiler warnings
+        fnCheckWErrors();
     }
     delete ir;
     return retval;