| /* |
| * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code 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 |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| * |
| */ |
| |
| #include "precompiled.hpp" |
| #include "compiler/compileBroker.hpp" |
| #include "compiler/directivesParser.hpp" |
| #include "memory/allocation.inline.hpp" |
| #include "memory/resourceArea.hpp" |
| #include "runtime/os.hpp" |
| #include <string.h> |
| |
| void DirectivesParser::push_tmp(CompilerDirectives* dir) { |
| _tmp_depth++; |
| dir->set_next(_tmp_top); |
| _tmp_top = dir; |
| } |
| |
| CompilerDirectives* DirectivesParser::pop_tmp() { |
| if (_tmp_top == NULL) { |
| return NULL; |
| } |
| CompilerDirectives* tmp = _tmp_top; |
| _tmp_top = _tmp_top->next(); |
| tmp->set_next(NULL); |
| _tmp_depth--; |
| return tmp; |
| } |
| |
| void DirectivesParser::clean_tmp() { |
| CompilerDirectives* tmp = pop_tmp(); |
| while (tmp != NULL) { |
| delete tmp; |
| tmp = pop_tmp(); |
| } |
| assert(_tmp_depth == 0, "Consistency"); |
| } |
| |
| int DirectivesParser::parse_string(const char* text, outputStream* st) { |
| DirectivesParser cd(text, st, false); |
| if (cd.valid()) { |
| return cd.install_directives(); |
| } else { |
| cd.clean_tmp(); |
| st->flush(); |
| st->print_cr("Parsing of compiler directives failed"); |
| return -1; |
| } |
| } |
| |
| bool DirectivesParser::has_file() { |
| return CompilerDirectivesFile != NULL; |
| } |
| |
| bool DirectivesParser::parse_from_flag() { |
| return parse_from_file(CompilerDirectivesFile, tty); |
| } |
| |
| bool DirectivesParser::parse_from_file(const char* filename, outputStream* st) { |
| assert(filename != NULL, "Test before calling this"); |
| if (!parse_from_file_inner(filename, st)) { |
| st->print_cr("Could not load file: %s", filename); |
| return false; |
| } |
| return true; |
| } |
| |
| bool DirectivesParser::parse_from_file_inner(const char* filename, outputStream* stream) { |
| struct stat st; |
| ResourceMark rm; |
| if (os::stat(filename, &st) == 0) { |
| // found file, open it |
| int file_handle = os::open(filename, 0, 0); |
| if (file_handle != -1) { |
| // read contents into resource array |
| char* buffer = NEW_RESOURCE_ARRAY(char, st.st_size+1); |
| size_t num_read = os::read(file_handle, (char*) buffer, st.st_size); |
| buffer[num_read] = '\0'; |
| // close file |
| os::close(file_handle); |
| return parse_string(buffer, stream) > 0; |
| } |
| } |
| return false; |
| } |
| |
| int DirectivesParser::install_directives() { |
| // Check limit |
| if (!DirectivesStack::check_capacity(_tmp_depth, _st)) { |
| clean_tmp(); |
| return 0; |
| } |
| |
| // Pop from internal temporary stack and push to compileBroker. |
| CompilerDirectives* tmp = pop_tmp(); |
| int i = 0; |
| while (tmp != NULL) { |
| i++; |
| DirectivesStack::push(tmp); |
| tmp = pop_tmp(); |
| } |
| if (i == 0) { |
| _st->print_cr("No directives in file"); |
| return 0; |
| } else { |
| _st->print_cr("%i compiler directives added", i); |
| if (CompilerDirectivesPrint) { |
| // Print entire directives stack after new has been pushed. |
| DirectivesStack::print(_st); |
| } |
| return i; |
| } |
| } |
| |
| DirectivesParser::DirectivesParser(const char* text, outputStream* st, bool silent) |
| : JSON(text, silent, st), depth(0), current_directive(NULL), current_directiveset(NULL), _tmp_top(NULL), _tmp_depth(0) { |
| #ifndef PRODUCT |
| memset(stack, 0, MAX_DEPTH * sizeof(stack[0])); |
| #endif |
| parse(); |
| } |
| |
| DirectivesParser::~DirectivesParser() { |
| assert(_tmp_top == NULL, "Consistency"); |
| assert(_tmp_depth == 0, "Consistency"); |
| } |
| |
| const DirectivesParser::key DirectivesParser::keys[] = { |
| // name, keytype, allow_array, allowed_mask, set_function |
| { "c1", type_c1, 0, mask(type_directives), NULL, UnknownFlagType }, |
| { "c2", type_c2, 0, mask(type_directives), NULL, UnknownFlagType }, |
| { "match", type_match, 1, mask(type_directives), NULL, UnknownFlagType }, |
| { "inline", type_inline, 1, mask(type_directives) | mask(type_c1) | mask(type_c2), NULL, UnknownFlagType }, |
| |
| // Global flags |
| #define common_flag_key(name, type, dvalue, compiler) \ |
| { #name, type_flag, 0, mask(type_directives) | mask(type_c1) | mask(type_c2), &DirectiveSet::set_##name, type##Flag}, |
| compilerdirectives_common_flags(common_flag_key) |
| compilerdirectives_c2_flags(common_flag_key) |
| compilerdirectives_c1_flags(common_flag_key) |
| #undef common_flag_key |
| }; |
| |
| const DirectivesParser::key DirectivesParser::dir_array_key = { |
| "top level directives array", type_dir_array, 0, 1 // Lowest bit means allow at top level |
| }; |
| const DirectivesParser::key DirectivesParser::dir_key = { |
| "top level directive", type_directives, 0, mask(type_dir_array) | 1 // Lowest bit means allow at top level |
| }; |
| const DirectivesParser::key DirectivesParser::value_array_key = { |
| "value array", type_value_array, 0, UINT_MAX // Allow all, checked by allow_array on other keys, not by allowed_mask from this key |
| }; |
| |
| const DirectivesParser::key* DirectivesParser::lookup_key(const char* str, size_t len) { |
| for (size_t i = 0; i < (sizeof(keys) / sizeof(keys[0])); i++) { |
| if (strncasecmp(keys[i].name, str, len) == 0) { |
| return &keys[i]; |
| } |
| } |
| return NULL; |
| } |
| |
| uint DirectivesParser::mask(keytype kt) { |
| return 1 << (kt + 1); |
| } |
| |
| bool DirectivesParser::push_key(const char* str, size_t len) { |
| bool result = true; |
| const key* k = lookup_key(str, len); |
| |
| if (k == NULL) { |
| // os::strdup |
| char* s = NEW_C_HEAP_ARRAY(char, len + 1, mtCompiler); |
| strncpy(s, str, len); |
| s[len] = '\0'; |
| error(KEY_ERROR, "No such key: '%s'.", s); |
| FREE_C_HEAP_ARRAY(char, s); |
| return false; |
| } |
| |
| return push_key(k); |
| } |
| |
| bool DirectivesParser::push_key(const key* k) { |
| assert(k->allowedmask != 0, "not allowed anywhere?"); |
| |
| // Exceeding the stack should not be possible with a valid compiler directive, |
| // and an invalid should abort before this happens |
| assert(depth < MAX_DEPTH, "exceeded stack depth"); |
| if (depth >= MAX_DEPTH) { |
| error(INTERNAL_ERROR, "Stack depth exceeded."); |
| return false; |
| } |
| |
| assert(stack[depth] == NULL, "element not nulled, something is wrong"); |
| |
| if (depth == 0 && !(k->allowedmask & 1)) { |
| error(KEY_ERROR, "Key '%s' not allowed at top level.", k->name); |
| return false; |
| } |
| |
| if (depth > 0) { |
| const key* prev = stack[depth - 1]; |
| if (!(k->allowedmask & mask(prev->type))) { |
| error(KEY_ERROR, "Key '%s' not allowed after '%s' key.", k->name, prev->name); |
| return false; |
| } |
| } |
| |
| stack[depth] = k; |
| depth++; |
| return true; |
| } |
| |
| const DirectivesParser::key* DirectivesParser::current_key() { |
| assert(depth > 0, "getting key from empty stack"); |
| if (depth == 0) { |
| return NULL; |
| } |
| return stack[depth - 1]; |
| } |
| |
| const DirectivesParser::key* DirectivesParser::pop_key() { |
| assert(depth > 0, "popping empty stack"); |
| if (depth == 0) { |
| error(INTERNAL_ERROR, "Popping empty stack."); |
| return NULL; |
| } |
| depth--; |
| |
| const key* k = stack[depth]; |
| #ifndef PRODUCT |
| stack[depth] = NULL; |
| #endif |
| |
| return k; |
| } |
| |
| bool DirectivesParser::set_option_flag(JSON_TYPE t, JSON_VAL* v, const key* option_key, DirectiveSet* set) { |
| |
| void (DirectiveSet::*test)(void *args); |
| test = option_key->set; |
| |
| switch (t) { |
| case JSON_TRUE: |
| if (option_key->flag_type != boolFlag) { |
| error(VALUE_ERROR, "Cannot use bool value for an %s flag", flag_type_names[option_key->flag_type]); |
| return false; |
| } else { |
| bool val = true; |
| (set->*test)((void *)&val); |
| } |
| break; |
| |
| case JSON_FALSE: |
| if (option_key->flag_type != boolFlag) { |
| error(VALUE_ERROR, "Cannot use bool value for an %s flag", flag_type_names[option_key->flag_type]); |
| return false; |
| } else { |
| bool val = false; |
| (set->*test)((void *)&val); |
| } |
| break; |
| |
| case JSON_NUMBER_INT: |
| if (option_key->flag_type == intxFlag) { |
| intx ival = v->int_value; |
| (set->*test)((void *)&ival); |
| } else if (option_key->flag_type == uintxFlag) { |
| uintx ival = v->uint_value; |
| (set->*test)((void *)&ival); |
| } else if (option_key->flag_type == doubleFlag) { |
| double dval = (double)v->int_value; |
| (set->*test)((void *)&dval); |
| } else { |
| error(VALUE_ERROR, "Cannot use int value for an %s flag", flag_type_names[option_key->flag_type]); |
| return false; |
| } |
| break; |
| |
| case JSON_NUMBER_FLOAT: |
| if (option_key->flag_type != doubleFlag) { |
| error(VALUE_ERROR, "Cannot use double value for an %s flag", flag_type_names[option_key->flag_type]); |
| return false; |
| } else { |
| double dval = v->double_value; |
| (set->*test)((void *)&dval); |
| } |
| break; |
| |
| case JSON_STRING: |
| if (option_key->flag_type != ccstrFlag && option_key->flag_type != ccstrlistFlag) { |
| error(VALUE_ERROR, "Cannot use string value for a %s flag", flag_type_names[option_key->flag_type]); |
| return false; |
| } else { |
| char* s = NEW_C_HEAP_ARRAY(char, v->str.length+1, mtCompiler); |
| strncpy(s, v->str.start, v->str.length + 1); |
| s[v->str.length] = '\0'; |
| (set->*test)((void *)&s); |
| } |
| break; |
| |
| default: |
| assert(0, "Should not reach here."); |
| } |
| return true; |
| } |
| |
| bool DirectivesParser::set_option(JSON_TYPE t, JSON_VAL* v) { |
| |
| const key* option_key = pop_key(); |
| const key* enclosing_key = current_key(); |
| |
| if (option_key->type == value_array_key.type) { |
| // Multi value array, we are really setting the value |
| // for the key one step further up. |
| option_key = pop_key(); |
| enclosing_key = current_key(); |
| |
| // Repush option_key and multi value marker, since |
| // we need to keep them until all multi values are set. |
| push_key(option_key); |
| push_key(&value_array_key); |
| } |
| |
| switch (option_key->type) { |
| case type_flag: |
| { |
| if (current_directiveset == NULL) { |
| assert(depth == 2, "Must not have active directive set"); |
| |
| if (!set_option_flag(t, v, option_key, current_directive->_c1_store)) { |
| return false; |
| } |
| if (!set_option_flag(t, v, option_key, current_directive->_c2_store)) { |
| return false; |
| } |
| } else { |
| assert(depth > 2, "Must have active current directive set"); |
| if (!set_option_flag(t, v, option_key, current_directiveset)) { |
| return false; |
| } |
| } |
| break; |
| } |
| |
| case type_match: |
| if (t != JSON_STRING) { |
| error(VALUE_ERROR, "Key of type %s needs a value of type string", option_key->name); |
| return false; |
| } |
| if (enclosing_key->type != type_directives) { |
| error(SYNTAX_ERROR, "Match keyword can only exist inside a directive"); |
| return false; |
| } |
| { |
| char* s = NEW_C_HEAP_ARRAY(char, v->str.length + 1, mtCompiler); |
| strncpy(s, v->str.start, v->str.length); |
| s[v->str.length] = '\0'; |
| |
| const char* error_msg = NULL; |
| if (!current_directive->add_match(s, error_msg)) { |
| assert (error_msg != NULL, "Must have valid error message"); |
| error(VALUE_ERROR, "Method pattern error: %s", error_msg); |
| } |
| FREE_C_HEAP_ARRAY(char, s); |
| } |
| break; |
| |
| case type_inline: |
| if (t != JSON_STRING) { |
| error(VALUE_ERROR, "Key of type %s needs a value of type string", option_key->name); |
| return false; |
| } |
| { |
| //char* s = strndup(v->str.start, v->str.length); |
| char* s = NEW_C_HEAP_ARRAY(char, v->str.length + 1, mtCompiler); |
| strncpy(s, v->str.start, v->str.length); |
| s[v->str.length] = '\0'; |
| |
| const char* error_msg = NULL; |
| if (current_directiveset == NULL) { |
| if (current_directive->_c1_store->parse_and_add_inline(s, error_msg)) { |
| if (!current_directive->_c2_store->parse_and_add_inline(s, error_msg)) { |
| assert (error_msg != NULL, "Must have valid error message"); |
| error(VALUE_ERROR, "Method pattern error: %s", error_msg); |
| } |
| } else { |
| assert (error_msg != NULL, "Must have valid error message"); |
| error(VALUE_ERROR, "Method pattern error: %s", error_msg); |
| } |
| } else { |
| if (!current_directiveset->parse_and_add_inline(s, error_msg)) { |
| assert (error_msg != NULL, "Must have valid error message"); |
| error(VALUE_ERROR, "Method pattern error: %s", error_msg); |
| } |
| } |
| FREE_C_HEAP_ARRAY(char, s); |
| } |
| break; |
| |
| case type_c1: |
| current_directiveset = current_directive->_c1_store; |
| if (t != JSON_TRUE && t != JSON_FALSE) { |
| error(VALUE_ERROR, "Key of type %s needs a true or false value", option_key->name); |
| return false; |
| } |
| break; |
| |
| case type_c2: |
| current_directiveset = current_directive->_c2_store; |
| if (t != JSON_TRUE && t != JSON_FALSE) { |
| error(VALUE_ERROR, "Key of type %s needs a true or false value", option_key->name); |
| return false; |
| } |
| break; |
| |
| default: |
| break; |
| } |
| |
| return true; |
| } |
| |
| bool DirectivesParser::callback(JSON_TYPE t, JSON_VAL* v, uint rlimit) { |
| const key* k; |
| |
| if (depth == 0) { |
| switch (t) { |
| case JSON_ARRAY_BEGIN: |
| return push_key(&dir_array_key); |
| |
| case JSON_OBJECT_BEGIN: |
| // push synthetic dir_array |
| push_key(&dir_array_key); |
| assert(depth == 1, "Make sure the stack are aligned with the directives"); |
| break; |
| |
| default: |
| error(SYNTAX_ERROR, "DirectivesParser can only start with an array containing directive objects, or one single directive."); |
| return false; |
| } |
| } |
| if (depth == 1) { |
| switch (t) { |
| case JSON_OBJECT_BEGIN: |
| // Parsing a new directive. |
| current_directive = new CompilerDirectives(); |
| return push_key(&dir_key); |
| |
| case JSON_ARRAY_END: |
| k = pop_key(); |
| |
| if (k->type != type_dir_array) { |
| error(SYNTAX_ERROR, "Expected end of directives array"); |
| return false; |
| } |
| return true; |
| |
| default: |
| error(SYNTAX_ERROR, "DirectivesParser can only start with an array containing directive objects, or one single directive."); |
| return false; |
| } |
| } else { |
| switch (t) { |
| case JSON_OBJECT_BEGIN: |
| k = current_key(); |
| switch (k->type) { |
| case type_c1: |
| current_directiveset = current_directive->_c1_store; |
| return true; |
| case type_c2: |
| current_directiveset = current_directive->_c2_store; |
| return true; |
| |
| case type_dir_array: |
| return push_key(&dir_key); |
| |
| default: |
| error(SYNTAX_ERROR, "The key '%s' does not allow an object to follow.", k->name); |
| return false; |
| } |
| return false; |
| |
| case JSON_OBJECT_END: |
| k = pop_key(); |
| switch (k->type) { |
| case type_c1: |
| case type_c2: |
| // This is how we now if options apply to a single or both directive sets |
| current_directiveset = NULL; |
| break; |
| |
| case type_directives: |
| // Check, finish and push to stack! |
| if (current_directive->match() == NULL) { |
| error(INTERNAL_ERROR, "Directive missing required match."); |
| return false; |
| } |
| current_directive->finalize(_st); |
| push_tmp(current_directive); |
| current_directive = NULL; |
| break; |
| |
| default: |
| error(INTERNAL_ERROR, "Object end with wrong key type on stack: %s.", k->name); |
| ShouldNotReachHere(); |
| return false; |
| } |
| return true; |
| |
| case JSON_ARRAY_BEGIN: |
| k = current_key(); |
| if (!(k->allow_array_value)) { |
| if (k->type == type_dir_array) { |
| error(SYNTAX_ERROR, "Array not allowed inside top level array, expected directive object."); |
| } else { |
| error(VALUE_ERROR, "The key '%s' does not allow an array of values.", k->name); |
| } |
| return false; |
| } |
| return push_key(&value_array_key); |
| |
| case JSON_ARRAY_END: |
| k = pop_key(); // Pop multi value marker |
| assert(k->type == value_array_key.type, "array end for level != 0 should terminate multi value"); |
| k = pop_key(); // Pop key for option that was set |
| return true; |
| |
| case JSON_KEY: |
| return push_key(v->str.start, v->str.length); |
| |
| case JSON_STRING: |
| case JSON_NUMBER_INT: |
| case JSON_NUMBER_FLOAT: |
| case JSON_TRUE: |
| case JSON_FALSE: |
| case JSON_NULL: |
| return set_option(t, v); |
| |
| default: |
| error(INTERNAL_ERROR, "Unknown JSON type: %d.", t); |
| ShouldNotReachHere(); |
| return false; |
| } |
| } |
| } |
| |
| #ifndef PRODUCT |
| void DirectivesParser::test(const char* text, bool should_pass) { |
| DirectivesParser cd(text, tty, !VerboseInternalVMTests); |
| if (should_pass) { |
| assert(cd.valid() == true, "failed on a valid DirectivesParser string"); |
| if (VerboseInternalVMTests) { |
| tty->print("-- DirectivesParser test passed as expected --\n"); |
| } |
| } else { |
| assert(cd.valid() == false, "succeeded on an invalid DirectivesParser string"); |
| if (VerboseInternalVMTests) { |
| tty->print("-- DirectivesParser test failed as expected --\n"); |
| } |
| } |
| cd.clean_tmp(); |
| } |
| |
| void DirectivesParser::test() { |
| DirectivesParser::test("{}", false); |
| DirectivesParser::test("[]", true); |
| DirectivesParser::test("[{}]", false); |
| DirectivesParser::test("[{},{}]", false); |
| DirectivesParser::test("{},{}", false); |
| |
| DirectivesParser::test( |
| "[" "\n" |
| " {" "\n" |
| " match: \"foo/bar.*\"," "\n" |
| " inline : \"+java/util.*\"," "\n" |
| " PrintAssembly: true," "\n" |
| " BreakAtExecute: true," "\n" |
| " }" "\n" |
| "]" "\n", true); |
| |
| DirectivesParser::test( |
| "[" "\n" |
| " [" "\n" |
| " {" "\n" |
| " match: \"foo/bar.*\"," "\n" |
| " inline : \"+java/util.*\"," "\n" |
| " PrintAssembly: true," "\n" |
| " BreakAtExecute: true," "\n" |
| " }" "\n" |
| " ]" "\n" |
| "]" "\n", false); |
| |
| /*DirectivesParser::test( |
| "[" "\n" |
| " {" "\n" |
| " match: \"foo/bar.*\"," "\n" |
| " c1: {" |
| " PrintIntrinsics: false," "\n" |
| " }" "\n" |
| " }" "\n" |
| "]" "\n", false);*/ |
| |
| DirectivesParser::test( |
| "[" "\n" |
| " {" "\n" |
| " match: \"foo/bar.*\"," "\n" |
| " c2: {" "\n" |
| " PrintInlining: false," "\n" |
| " }" "\n" |
| " }" "\n" |
| "]" "\n", true); |
| |
| DirectivesParser::test( |
| "[" "\n" |
| " {" "\n" |
| " match: \"foo/bar.*\"," "\n" |
| " c2: {" "\n" |
| " VectorizeDebug: 1," "\n" |
| " VectorizeDebug: -1," "\n" |
| " }" "\n" |
| " }" "\n" |
| "]" "\n", COMPILER2_PRESENT(true) NOT_COMPILER2(false)); |
| |
| DirectivesParser::test( |
| "[" "\n" |
| " {" "\n" |
| " match: \"foo/bar.*\"," "\n" |
| " PrintInlining: [" "\n" |
| " true," "\n" |
| " false" "\n" |
| " ]," "\n" |
| " }" "\n" |
| "]" "\n", false); |
| |
| DirectivesParser::test( |
| "[" "\n" |
| " {" |
| " // pattern to match against class+method+signature" "\n" |
| " // leading and trailing wildcard (*) allowed" "\n" |
| " match: \"foo/bar.*\"," "\n" |
| "" "\n" |
| " // override defaults for specified compiler" "\n" |
| " // we may differentiate between levels too. TBD." "\n" |
| " c1: {" "\n" |
| " //override c1 presets " "\n" |
| " DumpReplay: false," "\n" |
| " BreakAtCompile: true," "\n" |
| " }," "\n" |
| "" "\n" |
| " c2: {" "\n" |
| " // control inlining of method" "\n" |
| " // + force inline, - dont inline" "\n" |
| " inline : \"+java/util.*\"," "\n" |
| " PrintInlining: true," "\n" |
| " }," "\n" |
| "" "\n" |
| " // directives outside a specific preset applies to all compilers" "\n" |
| " inline : [ \"+java/util.*\", \"-com/sun.*\"]," "\n" |
| " BreakAtExecute: true," "\n" |
| " Log: true," "\n" |
| " }," "\n" |
| " {" "\n" |
| " // matching several patterns require an array" "\n" |
| " match: [\"baz.*\",\"frob.*\"]," "\n" |
| "" "\n" |
| " // applies to all compilers" "\n" |
| " // + force inline, - dont inline" "\n" |
| " inline : [ \"+java/util.*\", \"-com/sun.*\" ]," "\n" |
| " PrintInlining: true," "\n" |
| "" "\n" |
| " // force matching compiles to be blocking/syncronous" "\n" |
| " PrintNMethods: true" "\n" |
| " }," "\n" |
| "]" "\n", true); |
| |
| // Test max stack depth |
| DirectivesParser::test( |
| "[" "\n" // depth 1: type_dir_array |
| " {" "\n" // depth 2: type_directives |
| " match: \"*.*\"," // match required |
| " c1:" "\n" // depth 3: type_c1 |
| " {" "\n" |
| " inline:" "\n" // depth 4: type_inline |
| " [" "\n" // depth 5: type_value_array |
| " \"foo\"," "\n" |
| " \"bar\"," "\n" |
| " ]" "\n" // depth 3: pop type_value_array and type_inline keys |
| " }" "\n" // depth 2: pop type_c1 key |
| " }" "\n" // depth 1: pop type_directives key |
| "]" "\n", true); // depth 0: pop type_dir_array key |
| |
| // Test max stack depth |
| DirectivesParser::test( |
| "[{c1:{c1:{c1:{c1:{c1:{c1:{c1:{}}}}}}}}]", false); |
| |
| } |
| |
| void DirectivesParser_test() { |
| DirectivesParser::test(); |
| } |
| |
| #endif |