// script.cc -- handle linker scripts for gold.
-// Copyright 2006, 2007, 2008, 2009 Free Software Foundation, Inc.
+// Copyright 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
// Written by Ian Lance Taylor <iant@google.com>.
// This file is part of gold.
lex_(lex), lineno_(0), charpos_(0), lex_mode_stack_(), inputs_(NULL)
{
// We start out processing C symbols in the default lex mode.
- language_stack_.push_back("");
- lex_mode_stack_.push_back(lex->mode());
+ this->language_stack_.push_back(Version_script_info::LANGUAGE_C);
+ this->lex_mode_stack_.push_back(lex->mode());
}
// Return the file name.
// Return the current language being processed in a version script
// (eg, "C++"). The empty string represents unmangled C names.
- const std::string&
+ Version_script_info::Language
get_current_language() const
{ return this->language_stack_.back(); }
// Push a language onto the stack when entering an extern block.
- void push_language(const std::string& lang)
+ void
+ push_language(Version_script_info::Language lang)
{ this->language_stack_.push_back(lang); }
// Pop a language off of the stack when exiting an extern block.
- void pop_language()
+ void
+ pop_language()
{
gold_assert(!this->language_stack_.empty());
this->language_stack_.pop_back();
std::vector<Lex::Mode> lex_mode_stack_;
// A stack of which extern/language block we're inside. Can be C++,
// java, or empty for C.
- std::vector<std::string> language_stack_;
+ std::vector<Version_script_info::Language> language_stack_;
// New input files found to add to the link.
Input_arguments* inputs_;
};
&lex,
input_file->will_search_for());
+ bool old_saw_sections_clause =
+ layout->script_options()->saw_sections_clause();
+
if (yyparse(&closure) != 0)
{
if (closure.found_incompatible_target())
return false;
}
+ if (!old_saw_sections_clause
+ && layout->script_options()->saw_sections_clause()
+ && layout->have_added_input_section())
+ gold_error(_("%s: SECTIONS seen after other input files; try -T/--script"),
+ input_file->filename().c_str());
+
if (!closure.saw_inputs())
return true;
}
workqueue->queue_soon(new Read_symbols(input_objects, symtab,
layout, dirsearch, 0, mapfile, &*p,
- input_group, this_blocker, nb));
+ input_group, NULL, this_blocker, nb));
this_blocker = nb;
}
return ktt->parsecode;
}
+// The following structs are used within the VersionInfo class as well
+// as in the bison helper functions. They store the information
+// parsed from the version script.
+
+// A single version expression.
+// For example, pattern="std::map*" and language="C++".
+struct Version_expression
+{
+ Version_expression(const std::string& a_pattern,
+ Version_script_info::Language a_language,
+ bool a_exact_match)
+ : pattern(a_pattern), language(a_language), exact_match(a_exact_match),
+ was_matched_by_symbol(false)
+ { }
+
+ std::string pattern;
+ Version_script_info::Language language;
+ // If false, we use glob() to match pattern. If true, we use strcmp().
+ bool exact_match;
+ // True if --no-undefined-version is in effect and we found this
+ // version in get_symbol_version. We use mutable because this
+ // struct is generally not modifiable after it has been created.
+ mutable bool was_matched_by_symbol;
+};
+
+// A list of expressions.
+struct Version_expression_list
+{
+ std::vector<struct Version_expression> expressions;
+};
+
+// A list of which versions upon which another version depends.
+// Strings should be from the Stringpool.
+struct Version_dependency_list
+{
+ std::vector<std::string> dependencies;
+};
+
+// The total definition of a version. It includes the tag for the
+// version, its global and local expressions, and any dependencies.
+struct Version_tree
+{
+ Version_tree()
+ : tag(), global(NULL), local(NULL), dependencies(NULL)
+ { }
+
+ std::string tag;
+ const struct Version_expression_list* global;
+ const struct Version_expression_list* local;
+ const struct Version_dependency_list* dependencies;
+};
+
// Helper class that calls cplus_demangle when needed and takes care of freeing
// the result.
// Return the demangled name. The actual demangling happens on the first call,
// and the result is later cached.
-
inline char*
get();
return this->demangled_;
}
-// The following structs are used within the VersionInfo class as well
-// as in the bison helper functions. They store the information
-// parsed from the version script.
-
-// A single version expression.
-// For example, pattern="std::map*" and language="C++".
-// pattern and language should be from the stringpool
-struct Version_expression {
- Version_expression(const std::string& pattern,
- const std::string& language,
- bool exact_match)
- : pattern(pattern), language(language), exact_match(exact_match) {}
+// Class Version_script_info.
- std::string pattern;
- std::string language;
- // If false, we use glob() to match pattern. If true, we use strcmp().
- bool exact_match;
-};
-
-
-// A list of expressions.
-struct Version_expression_list {
- std::vector<struct Version_expression> expressions;
-};
-
-
-// A list of which versions upon which another version depends.
-// Strings should be from the Stringpool.
-struct Version_dependency_list {
- std::vector<std::string> dependencies;
-};
-
-
-// The total definition of a version. It includes the tag for the
-// version, its global and local expressions, and any dependencies.
-struct Version_tree {
- Version_tree()
- : tag(), global(NULL), local(NULL), dependencies(NULL) {}
-
- std::string tag;
- const struct Version_expression_list* global;
- const struct Version_expression_list* local;
- const struct Version_dependency_list* dependencies;
-};
+Version_script_info::Version_script_info()
+ : dependency_lists_(), expression_lists_(), version_trees_(), globs_(),
+ default_version_(NULL), default_is_global_(false), is_finalized_(false)
+{
+ for (int i = 0; i < LANGUAGE_COUNT; ++i)
+ this->exact_[i] = NULL;
+}
Version_script_info::~Version_script_info()
{
- this->clear();
}
+// Forget all the known version script information.
+
void
Version_script_info::clear()
{
- for (size_t k = 0; k < dependency_lists_.size(); ++k)
- delete dependency_lists_[k];
+ for (size_t k = 0; k < this->dependency_lists_.size(); ++k)
+ delete this->dependency_lists_[k];
this->dependency_lists_.clear();
- for (size_t k = 0; k < version_trees_.size(); ++k)
- delete version_trees_[k];
+ for (size_t k = 0; k < this->version_trees_.size(); ++k)
+ delete this->version_trees_[k];
this->version_trees_.clear();
- for (size_t k = 0; k < expression_lists_.size(); ++k)
- delete expression_lists_[k];
+ for (size_t k = 0; k < this->expression_lists_.size(); ++k)
+ delete this->expression_lists_[k];
this->expression_lists_.clear();
}
+// Finalize the version script information.
+
+void
+Version_script_info::finalize()
+{
+ if (!this->is_finalized_)
+ {
+ this->build_lookup_tables();
+ this->is_finalized_ = true;
+ }
+}
+
+// Return all the versions.
+
std::vector<std::string>
Version_script_info::get_versions() const
{
std::vector<std::string> ret;
- for (size_t j = 0; j < version_trees_.size(); ++j)
+ for (size_t j = 0; j < this->version_trees_.size(); ++j)
if (!this->version_trees_[j]->tag.empty())
ret.push_back(this->version_trees_[j]->tag);
return ret;
}
+// Return the dependencies of VERSION.
+
std::vector<std::string>
Version_script_info::get_dependencies(const char* version) const
{
std::vector<std::string> ret;
- for (size_t j = 0; j < version_trees_.size(); ++j)
- if (version_trees_[j]->tag == version)
+ for (size_t j = 0; j < this->version_trees_.size(); ++j)
+ if (this->version_trees_[j]->tag == version)
{
const struct Version_dependency_list* deps =
- version_trees_[j]->dependencies;
+ this->version_trees_[j]->dependencies;
if (deps != NULL)
for (size_t k = 0; k < deps->dependencies.size(); ++k)
ret.push_back(deps->dependencies[k]);
return ret;
}
-// Look up SYMBOL_NAME in the list of versions. If CHECK_GLOBAL is
-// true look at the globally visible symbols, otherwise look at the
-// symbols listed as "local:". Return true if the symbol is found,
-// false otherwise. If the symbol is found, then if PVERSION is not
-// NULL, set *PVERSION to the version.
+// A version script essentially maps a symbol name to a version tag
+// and an indication of whether symbol is global or local within that
+// version tag. Each symbol maps to at most one version tag.
+// Unfortunately, in practice, version scripts are ambiguous, and list
+// symbols multiple times. Thus, we have to document the matching
+// process.
+
+// This is a description of what the GNU linker does as of 2010-01-11.
+// It walks through the version tags in the order in which they appear
+// in the version script. For each tag, it first walks through the
+// global patterns for that tag, then the local patterns. When
+// looking at a single pattern, it first applies any language specific
+// demangling as specified for the pattern, and then matches the
+// resulting symbol name to the pattern. If it finds an exact match
+// for a literal pattern (a pattern enclosed in quotes or with no
+// wildcard characters), then that is the match that it uses. If
+// finds a match with a wildcard pattern, then it saves it and
+// continues searching. Wildcard patterns that are exactly "*" are
+// saved separately.
+
+// If no exact match with a literal pattern is ever found, then if a
+// wildcard match with a global pattern was found it is used,
+// otherwise if a wildcard match with a local pattern was found it is
+// used.
+
+// This is the result:
+// * If there is an exact match, then we use the first tag in the
+// version script where it matches.
+// + If the exact match in that tag is global, it is used.
+// + Otherwise the exact match in that tag is local, and is used.
+// * Otherwise, if there is any match with a global wildcard pattern:
+// + If there is any match with a wildcard pattern which is not
+// "*", then we use the tag in which the *last* such pattern
+// appears.
+// + Otherwise, we matched "*". If there is no match with a local
+// wildcard pattern which is not "*", then we use the *last*
+// match with a global "*". Otherwise, continue.
+// * Otherwise, if there is any match with a local wildcard pattern:
+// + If there is any match with a wildcard pattern which is not
+// "*", then we use the tag in which the *last* such pattern
+// appears.
+// + Otherwise, we matched "*", and we use the tag in which the
+// *last* such match occurred.
+
+// There is an additional wrinkle. When the GNU linker finds a symbol
+// with a version defined in an object file due to a .symver
+// directive, it looks up that symbol name in that version tag. If it
+// finds it, it matches the symbol name against the patterns for that
+// version. If there is no match with a global pattern, but there is
+// a match with a local pattern, then the GNU linker marks the symbol
+// as local.
+
+// We want gold to be generally compatible, but we also want gold to
+// be fast. These are the rules that gold implements:
+// * If there is an exact match for the mangled name, we use it.
+// + If there is more than one exact match, we give a warning, and
+// we use the first tag in the script which matches.
+// + If a symbol has an exact match as both global and local for
+// the same version tag, we give an error.
+// * Otherwise, we look for an extern C++ or an extern Java exact
+// match. If we find an exact match, we use it.
+// + If there is more than one exact match, we give a warning, and
+// we use the first tag in the script which matches.
+// + If a symbol has an exact match as both global and local for
+// the same version tag, we give an error.
+// * Otherwise, we look through the wildcard patterns, ignoring "*"
+// patterns. We look through the version tags in reverse order.
+// For each version tag, we look through the global patterns and
+// then the local patterns. We use the first match we find (i.e.,
+// the last matching version tag in the file).
+// * Otherwise, we use the "*" pattern if there is one. We give an
+// error if there are multiple "*" patterns.
+
+// At least for now, gold does not look up the version tag for a
+// symbol version found in an object file to see if it should be
+// forced local. There are other ways to force a symbol to be local,
+// and I don't understand why this one is useful.
+
+// Build a set of fast lookup tables for a version script.
+
+void
+Version_script_info::build_lookup_tables()
+{
+ size_t size = this->version_trees_.size();
+ for (size_t j = 0; j < size; ++j)
+ {
+ const Version_tree* v = this->version_trees_[j];
+ this->build_expression_list_lookup(v->local, v, false);
+ this->build_expression_list_lookup(v->global, v, true);
+ }
+}
+
+// If a pattern has backlashes but no unquoted wildcard characters,
+// then we apply backslash unquoting and look for an exact match.
+// Otherwise we treat it as a wildcard pattern. This function returns
+// true for a wildcard pattern. Otherwise, it does backslash
+// unquoting on *PATTERN and returns false. If this returns true,
+// *PATTERN may have been partially unquoted.
bool
-Version_script_info::get_symbol_version_helper(const char* symbol_name,
- bool check_global,
- std::string* pversion) const
+Version_script_info::unquote(std::string* pattern) const
+{
+ bool saw_backslash = false;
+ size_t len = pattern->length();
+ size_t j = 0;
+ for (size_t i = 0; i < len; ++i)
+ {
+ if (saw_backslash)
+ saw_backslash = false;
+ else
+ {
+ switch ((*pattern)[i])
+ {
+ case '?': case '[': case '*':
+ return true;
+ case '\\':
+ saw_backslash = true;
+ continue;
+ default:
+ break;
+ }
+ }
+
+ if (i != j)
+ (*pattern)[j] = (*pattern)[i];
+ ++j;
+ }
+ return false;
+}
+
+// Add an exact match for MATCH to *PE. The result of the match is
+// V/IS_GLOBAL.
+
+void
+Version_script_info::add_exact_match(const std::string& match,
+ const Version_tree* v, bool is_global,
+ const Version_expression* ve,
+ Exact *pe)
+{
+ std::pair<Exact::iterator, bool> ins =
+ pe->insert(std::make_pair(match, Version_tree_match(v, is_global, ve)));
+ if (ins.second)
+ {
+ // This is the first time we have seen this match.
+ return;
+ }
+
+ Version_tree_match& vtm(ins.first->second);
+ if (vtm.real->tag != v->tag)
+ {
+ // This is an ambiguous match. We still return the
+ // first version that we found in the script, but we
+ // record the new version to issue a warning if we
+ // wind up looking up this symbol.
+ if (vtm.ambiguous == NULL)
+ vtm.ambiguous = v;
+ }
+ else if (is_global != vtm.is_global)
+ {
+ // We have a match for both the global and local entries for a
+ // version tag. That's got to be wrong.
+ gold_error(_("'%s' appears as both a global and a local symbol "
+ "for version '%s' in script"),
+ match.c_str(), v->tag.c_str());
+ }
+}
+
+// Build fast lookup information for EXPLIST and store it in LOOKUP.
+// All matches go to V, and IS_GLOBAL is true if they are global
+// matches.
+
+void
+Version_script_info::build_expression_list_lookup(
+ const Version_expression_list* explist,
+ const Version_tree* v,
+ bool is_global)
+{
+ if (explist == NULL)
+ return;
+ size_t size = explist->expressions.size();
+ for (size_t i = 0; i < size; ++i)
+ {
+ const Version_expression& exp(explist->expressions[i]);
+
+ if (exp.pattern.length() == 1 && exp.pattern[0] == '*')
+ {
+ if (this->default_version_ != NULL
+ && this->default_version_->tag != v->tag)
+ gold_warning(_("wildcard match appears in both version '%s' "
+ "and '%s' in script"),
+ this->default_version_->tag.c_str(), v->tag.c_str());
+ else if (this->default_version_ != NULL
+ && this->default_is_global_ != is_global)
+ gold_error(_("wildcard match appears as both global and local "
+ "in version '%s' in script"),
+ v->tag.c_str());
+ this->default_version_ = v;
+ this->default_is_global_ = is_global;
+ continue;
+ }
+
+ std::string pattern = exp.pattern;
+ if (!exp.exact_match)
+ {
+ if (this->unquote(&pattern))
+ {
+ this->globs_.push_back(Glob(&exp, v, is_global));
+ continue;
+ }
+ }
+
+ if (this->exact_[exp.language] == NULL)
+ this->exact_[exp.language] = new Exact();
+ this->add_exact_match(pattern, v, is_global, &exp,
+ this->exact_[exp.language]);
+ }
+}
+
+// Return the name to match given a name, a language code, and two
+// lazy demanglers.
+
+const char*
+Version_script_info::get_name_to_match(const char* name,
+ int language,
+ Lazy_demangler* cpp_demangler,
+ Lazy_demangler* java_demangler) const
+{
+ switch (language)
+ {
+ case LANGUAGE_C:
+ return name;
+ case LANGUAGE_CXX:
+ return cpp_demangler->get();
+ case LANGUAGE_JAVA:
+ return java_demangler->get();
+ default:
+ gold_unreachable();
+ }
+}
+
+// Look up SYMBOL_NAME in the list of versions. Return true if the
+// symbol is found, false if not. If the symbol is found, then if
+// PVERSION is not NULL, set *PVERSION to the version tag, and if
+// P_IS_GLOBAL is not NULL, set *P_IS_GLOBAL according to whether the
+// symbol is global or not.
+
+bool
+Version_script_info::get_symbol_version(const char* symbol_name,
+ std::string* pversion,
+ bool* p_is_global) const
{
Lazy_demangler cpp_demangled_name(symbol_name, DMGL_ANSI | DMGL_PARAMS);
Lazy_demangler java_demangled_name(symbol_name,
- DMGL_ANSI | DMGL_PARAMS | DMGL_JAVA);
- for (size_t j = 0; j < version_trees_.size(); ++j)
+ DMGL_ANSI | DMGL_PARAMS | DMGL_JAVA);
+
+ gold_assert(this->is_finalized_);
+ for (int i = 0; i < LANGUAGE_COUNT; ++i)
{
- // Is it a global symbol for this version?
- const Version_expression_list* explist =
- check_global ? version_trees_[j]->global : version_trees_[j]->local;
- if (explist != NULL)
- for (size_t k = 0; k < explist->expressions.size(); ++k)
- {
- const char* name_to_match = symbol_name;
- const struct Version_expression& exp = explist->expressions[k];
- if (exp.language == "C++")
- {
- name_to_match = cpp_demangled_name.get();
- // This isn't a C++ symbol.
- if (name_to_match == NULL)
- continue;
- }
- else if (exp.language == "Java")
- {
- name_to_match = java_demangled_name.get();
- // This isn't a Java symbol.
- if (name_to_match == NULL)
- continue;
- }
- bool matched;
- if (exp.exact_match)
- matched = strcmp(exp.pattern.c_str(), name_to_match) == 0;
- else
- matched = fnmatch(exp.pattern.c_str(), name_to_match,
- FNM_NOESCAPE) == 0;
- if (matched)
- {
- if (pversion != NULL)
- *pversion = this->version_trees_[j]->tag;
- return true;
- }
- }
+ Exact* exact = this->exact_[i];
+ if (exact == NULL)
+ continue;
+
+ const char* name_to_match = this->get_name_to_match(symbol_name, i,
+ &cpp_demangled_name,
+ &java_demangled_name);
+ if (name_to_match == NULL)
+ {
+ // If the name can not be demangled, the GNU linker goes
+ // ahead and tries to match it anyhow. That does not
+ // make sense to me and I have not implemented it.
+ continue;
+ }
+
+ Exact::const_iterator pe = exact->find(name_to_match);
+ if (pe != exact->end())
+ {
+ const Version_tree_match& vtm(pe->second);
+ if (vtm.ambiguous != NULL)
+ gold_warning(_("using '%s' as version for '%s' which is also "
+ "named in version '%s' in script"),
+ vtm.real->tag.c_str(), name_to_match,
+ vtm.ambiguous->tag.c_str());
+
+ if (pversion != NULL)
+ *pversion = vtm.real->tag;
+ if (p_is_global != NULL)
+ *p_is_global = vtm.is_global;
+
+ // If we are using --no-undefined-version, and this is a
+ // global symbol, we have to record that we have found this
+ // symbol, so that we don't warn about it. We have to do
+ // this now, because otherwise we have no way to get from a
+ // non-C language back to the demangled name that we
+ // matched.
+ if (p_is_global != NULL && vtm.is_global)
+ vtm.expression->was_matched_by_symbol = true;
+
+ return true;
+ }
+ }
+
+ // Look through the glob patterns in reverse order.
+
+ for (Globs::const_reverse_iterator p = this->globs_.rbegin();
+ p != this->globs_.rend();
+ ++p)
+ {
+ int language = p->expression->language;
+ const char* name_to_match = this->get_name_to_match(symbol_name,
+ language,
+ &cpp_demangled_name,
+ &java_demangled_name);
+ if (name_to_match == NULL)
+ continue;
+
+ if (fnmatch(p->expression->pattern.c_str(), name_to_match,
+ FNM_NOESCAPE) == 0)
+ {
+ if (pversion != NULL)
+ *pversion = p->version->tag;
+ if (p_is_global != NULL)
+ *p_is_global = p->is_global;
+ return true;
+ }
}
+
+ // Finally, there may be a wildcard.
+ if (this->default_version_ != NULL)
+ {
+ if (pversion != NULL)
+ *pversion = this->default_version_->tag;
+ if (p_is_global != NULL)
+ *p_is_global = this->default_is_global_;
+ return true;
+ }
+
return false;
}
+// Give an error if any exact symbol names (not wildcards) appear in a
+// version script, but there is no such symbol.
+
+void
+Version_script_info::check_unmatched_names(const Symbol_table* symtab) const
+{
+ for (size_t i = 0; i < this->version_trees_.size(); ++i)
+ {
+ const Version_tree* vt = this->version_trees_[i];
+ if (vt->global == NULL)
+ continue;
+ for (size_t j = 0; j < vt->global->expressions.size(); ++j)
+ {
+ const Version_expression& expression(vt->global->expressions[j]);
+
+ // Ignore cases where we used the version because we saw a
+ // symbol that we looked up. Note that
+ // WAS_MATCHED_BY_SYMBOL will be true even if the symbol was
+ // not a definition. That's OK as in that case we most
+ // likely gave an undefined symbol error anyhow.
+ if (expression.was_matched_by_symbol)
+ continue;
+
+ // Just ignore names which are in languages other than C.
+ // We have no way to look them up in the symbol table.
+ if (expression.language != LANGUAGE_C)
+ continue;
+
+ // Remove backslash quoting, and ignore wildcard patterns.
+ std::string pattern = expression.pattern;
+ if (!expression.exact_match)
+ {
+ if (this->unquote(&pattern))
+ continue;
+ }
+
+ if (symtab->lookup(pattern.c_str(), vt->tag.c_str()) == NULL)
+ gold_error(_("version script assignment of %s to symbol %s "
+ "failed: symbol not defined"),
+ vt->tag.c_str(), pattern.c_str());
+ }
+ }
+}
+
struct Version_dependency_list*
Version_script_info::allocate_dependency_list()
{
FILE* f,
const Version_expression_list* vel) const
{
- std::string current_language;
+ Version_script_info::Language current_language = LANGUAGE_C;
for (size_t i = 0; i < vel->expressions.size(); ++i)
{
const Version_expression& ve(vel->expressions[i]);
if (ve.language != current_language)
{
- if (!current_language.empty())
+ if (current_language != LANGUAGE_C)
fprintf(f, " }\n");
- fprintf(f, " extern \"%s\" {\n", ve.language.c_str());
+ switch (ve.language)
+ {
+ case LANGUAGE_C:
+ break;
+ case LANGUAGE_CXX:
+ fprintf(f, " extern \"C++\" {\n");
+ break;
+ case LANGUAGE_JAVA:
+ fprintf(f, " extern \"Java\" {\n");
+ break;
+ default:
+ gold_unreachable();
+ }
current_language = ve.language;
}
fprintf(f, " ");
- if (!current_language.empty())
+ if (current_language != LANGUAGE_C)
fprintf(f, " ");
if (ve.exact_match)
fprintf(f, "\n");
}
- if (!current_language.empty())
+ if (current_language != LANGUAGE_C)
fprintf(f, " }\n");
}
script_push_lex_into_version_mode(void* closurev)
{
Parser_closure* closure = static_cast<Parser_closure*>(closurev);
+ if (closure->version_script()->is_finalized())
+ gold_error(_("%s:%d:%d: invalid use of VERSION in input file"),
+ closure->filename(), closure->lineno(), closure->charpos());
closure->push_lex_mode(Lex::VERSION_SCRIPT);
}
}
// Add a pattern expression to an existing list of expressions, if any.
-// TODO: In the old linker, the last argument used to be a bool, but I
-// don't know what it meant.
extern "C" struct Version_expression_list *
script_new_vers_pattern(void* closurev,
version_script_push_lang(void* closurev, const char* lang, int langlen)
{
Parser_closure* closure = static_cast<Parser_closure*>(closurev);
- closure->push_language(std::string(lang, langlen));
+ std::string language(lang, langlen);
+ Version_script_info::Language code;
+ if (language.empty() || language == "C")
+ code = Version_script_info::LANGUAGE_C;
+ else if (language == "C++")
+ code = Version_script_info::LANGUAGE_CXX;
+ else if (language == "Java")
+ code = Version_script_info::LANGUAGE_JAVA;
+ else
+ {
+ char* buf = new char[langlen + 100];
+ snprintf(buf, langlen + 100,
+ _("unrecognized version script language '%s'"),
+ language.c_str());
+ yyerror(closurev, buf);
+ delete[] buf;
+ code = Version_script_info::LANGUAGE_C;
+ }
+ closure->push_language(code);
}
extern "C" void