/* * Copyright (c) 2021-2025 Symas Corporation * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of the Symas Corporation nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "cobol-system.h" #include "coretypes.h" #include "tree.h" #include "../../libgcobol/ec.h" #include "../../libgcobol/common-defs.h" #include "util.h" #include "cbldiag.h" #include "symbols.h" #include "inspect.h" #include "../../libgcobol/io.h" #include "genapi.h" extern int yydebug; static bool is_data_field( symbol_elem_t& e ) { if( e.type != SymField ) return false; auto f = cbl_field_of(&e); if( f->name[0] == '\0' ) return false; if( is_filler(f) ) return false; return f->type != FldForward; } class sym_name_t { public: // TEMPORARY const char *name; size_t program, parent; public: explicit sym_name_t( const char name[] ) : name(name), program(0), parent(0) { assert(name[0] == '\0'); } sym_name_t( size_t program, const char name[], size_t parent ) : name(name), program(program), parent(parent) {} const char * c_str() const { return name; } // Order by: Program, Name, Parent. bool operator<( const sym_name_t& that ) const { if( program == that.program ) { int by_name = strcasecmp(name, that.name); return by_name == 0? parent < that.parent : by_name < 0; } return program < that.program; } bool operator==( const char *name ) const { return strcasecmp(this->name, name) == 0; } bool same_program( size_t program ) const { return program == this->program; } }; typedef std::map< sym_name_t, std::vector<size_t> > symbol_map_t; static symbol_map_t symbol_map; typedef std::map <field_key_t, std::list<size_t> > field_keymap_t; static field_keymap_t symbol_map2; /* * As each field is added to the symbol table, add its name and index * to the name map. Initially the type is FldInvalid. Those are * removed by symbols_update(); */ void update_symbol_map2( const symbol_elem_t *e ) { auto field = cbl_field_of(e); if( ! field->is_typedef() ) { switch( field->type ) { case FldForward: case FldLiteralN: return; case FldLiteralA: if( ! field->is_key_name() ) return; break; default: break; } } field_key_t fk( e->program, field ); symbol_map2[fk].push_back(symbol_index(e)); } /* * Purge any field whose type is FldInvalid. Remove any names that do * not map to any field. */ void finalize_symbol_map2() { std::set<field_key_t> empties; for( auto& elem : symbol_map2 ) { auto& fields( elem.second ); std::remove_if( fields.begin(), fields.end(), []( auto isym ) { auto f = cbl_field_of(symbol_at(isym)); return f->type == FldInvalid; } ); if( fields.empty() ) empties.insert(elem.first); } for( const auto& key : empties ) { symbol_map2.erase(key); } } static void dump_symbol_map2( const field_key_t& key, const std::list<size_t>& candidates ) { if( !yydebug ) return; char *fields = NULL, sep[2] = ""; for( auto candidate : candidates ) { char *tmp = fields; fields = xasprintf("%s%s %3zu", tmp? tmp : "", sep, candidate); sep[0] = ','; free(tmp); } dbgmsg( "%s:%d: %3zu %s {%s}", __func__, __LINE__, key.program, key.name, fields ); free(fields); } void dump_symbol_map2() { int n = 0; for( const auto& elem : symbol_map2 ) { const field_key_t& key( elem.first ); const std::list<size_t>& candidates( elem.second); if( key.program != 0 ) { dump_symbol_map2( key, candidates ); n++; } } dbgmsg("symbol_map2 has %d program elements", n); } static void dump_symbol_map_value( const char name[], const symbol_map_t::value_type& value ) { if( !yydebug ) return; char *ancestry = NULL, sep[2] = ""; auto p = value.second.begin(); for( ; p != value.second.end(); p++ ) { char *tmp = ancestry; ancestry = xasprintf("%s%s %3zu", tmp? tmp : "", sep, *p); sep[0] = ','; free(tmp); } dbgmsg( "%s:%d: %s -> %-24s {%s }", __func__, __LINE__, name, value.first.c_str(), ancestry ); free(ancestry); } static void dump_symbol_map_value1( const symbol_map_t::value_type& value ) { dump_symbol_map_value( "result", value ); } static symbol_map_t::value_type field_structure( symbol_elem_t& sym ) { static const symbol_map_t::value_type none( symbol_map_t::key_type( 0, "", 0 ), std::vector<size_t>() ); if( getenv(__func__) && sym.type == SymField ) { const auto& field = *cbl_field_of(&sym); dbgmsg("%s: #%zu %s: '%s' is_data_field: %s", __func__, symbol_index(&sym), cbl_field_type_str(field.type), field.name, is_data_field(sym)? "yes" : "no" ); } if( !is_data_field(sym) ) return none; cbl_field_t *field = cbl_field_of(&sym); symbol_map_t::key_type key( sym.program, field->name, field->parent ); symbol_map_t::value_type elem( key, std::vector<size_t>() ); symbol_map_t::mapped_type& v(elem.second); for( v.push_back(field_index(field)); field->parent > 0; ) { symbol_elem_t *par = symbol_at(field->parent); if( SymFile == par->type ) { v.push_back(field->parent); break; } assert( SymField == par->type ); v.push_back(field->parent); field = cbl_field_of(par); // for C of R and B of A, where R redefines B, skip B: vector is [C, R, A]. cbl_field_t *redefined = symbol_redefines(field); // if R redefines B if( redefined ) { field = redefined; // We will use B's parent on next iteration } } if( getenv(__func__) && yydebug ) { dbgmsg( "%s:%d: '%s' has %zu ancestors", __func__, __LINE__, elem.first.c_str(), elem.second.size() ); dump_symbol_map_value(__func__, elem); } return elem; } void erase_symbol_map_fwds( size_t beg ) { for( auto p = symbols_begin(beg); p < symbols_end(); p++ ) { if( p->type != SymField ) continue; const auto& field(*cbl_field_of(p)); if( field.type == FldForward ) { symbol_map.erase( sym_name_t(p->program, field.name, field.parent) ); } } } void build_symbol_map() { static size_t beg = 0; size_t end = symbols_end() - symbols_begin(); if( beg == end ) return; const size_t nsym = end - beg; std::transform( symbols_begin(beg), symbols_end(), std::inserter(symbol_map, symbol_map.begin()), field_structure ); beg = end; symbol_map.erase(sym_name_t("")); if( yydebug ) { dbgmsg( "%s:%d: %zu of %zu symbols inserted into %zu in symbol_map", __func__, __LINE__, nsym, end, symbol_map.size() ); if( getenv(__func__) ) { for( const auto& elem : symbol_map ) { dump_symbol_map_value1(elem); } } } } bool update_symbol_map( symbol_elem_t *e ) { auto output = symbol_map.insert(field_structure(*e)); return output.second; } class is_name { const char *name; public: is_name( const char *name ) : name(name) {} bool operator()( symbol_map_t::value_type& elem ) { const bool tf = elem.first == name; if( tf && getenv("is_name") ) { dump_key( "matched", elem.first ); } return tf; } protected: void dump_key( const char tag[], const symbol_map_t::key_type& key ) const { dbgmsg( "symbol_map key: %s { %3zu %3zu %s }", tag, key.program, key.parent, key.name ); } }; /* * Construct a list of ancestors based on a set of candidate groups. * Presented with an item, see if any group an ancestor. If so, * replace the item's ancestry with the group's ancestry (thus * shortening the chain). Otherwise, return an empty element. */ class reduce_ancestry { std::vector<symbol_map_t::mapped_type> candidates; static symbol_map_t::mapped_type candidates_only( const symbol_map_t::value_type& elem ) { return elem.second; } public: reduce_ancestry( const symbol_map_t& groups ) : candidates( groups.size() ) { std::transform( groups.begin(), groups.end(), candidates.begin(), candidates_only ); } symbol_map_t::value_type reduce( const symbol_map_t::value_type& item ) { static symbol_map_t::value_type none( "", std::vector<size_t>() ); auto ancestors = candidates.begin(); for( ; ancestors != candidates.end(); ancestors++ ) { assert(!ancestors->empty()); // ancestry always starts with self auto p = std::find( item.second.begin(), item.second.end(), ancestors->front() ); if( p != item.second.end() ) { // Preserve symbol's index at front of ancestor list. symbol_map_t::mapped_type shorter(1 + ancestors->size()); auto p = shorter.begin(); *p = item.second.front(); shorter.insert( ++p, ancestors->begin(), ancestors->end() ); return make_pair(item.first, shorter); } } return none; } symbol_map_t::value_type operator()( symbol_map_t::value_type item ) { return reduce(item); } }; class different_program { size_t program; public: different_program( size_t program ) : program(program) {} bool operator()( const symbol_map_t::value_type& item ) const { return ! item.first.same_program(program); } }; class in_scope { size_t program; static size_t prog_of( size_t program ) { auto L = cbl_label_of(symbol_at(program)); return L->parent; } public: in_scope( size_t program ) : program(program) {} // A symbol is in scope if it's defined by this program or by an ancestor. bool operator()( const symbol_map_t::value_type& item ) const { symbol_elem_t *e = symbol_at(item.second.front()); for( size_t prog = this->program; prog != 0; prog = prog_of(prog) ) { if( e->program == prog ) return true; } return false; } }; /* * For a field symbol and list of qualifier IN/OF names, see if the * namelist matches the symbol's name and ancectors' names. Success * is all names match within scope. * * All symbols local to the program are in scope. A containing * program's symbol matches only if global_e is set on it or one of * its ancestors. */ static bool name_has_names( const symbol_elem_t *e, const std::list<const char *>& names, bool in_scope ) { assert( ! names.empty() ); auto name = names.rbegin(); while( e && e->type == SymField ) { auto field = cbl_field_of(e); if( field->type == FldForward ) return false; if( 0 == strcasecmp(field->name, *name) ) { in_scope = in_scope || (field->attr & global_e); if( ++name == names.rend() ) break; } // first name must match if( name == names.rbegin() ) break; // Do not chase redefines if we have an 01 record for an FD. if( field->file ) { e = symbol_at(field->file); assert(1 == field->level); assert(e->type == SymFile); break; } /* * If the current field redefines another, it is not a member of * its parent, but of its grandparent, if any. Not a loop because * REDEFINES cannot be chained. */ cbl_field_t *parent = symbol_redefines(field); if( parent ) { field = parent; assert( NULL == symbol_redefines(field) ); } e = field->parent ? symbol_at(field->parent) : NULL; } if( e && e->type == SymFile ) { // first name can be a filename auto file = cbl_file_of(e); if( 0 == strcasecmp(file->name, *name) ) name++; } return in_scope && name == names.rend(); } size_t end_of_group( size_t igroup ); static std::vector<size_t> symbol_match2( size_t program, std::list<const char *> names, bool local = true ) { std::vector<size_t> fields; field_key_t key(program, names.back()); auto plist = symbol_map2.find(key); if( plist != symbol_map2.end() ) { for( auto candidate : plist->second ) { auto e = symbol_at(candidate); if( name_has_names( e, names, local ) ) { fields.push_back( symbol_index(e) ); } } } if( fields.empty() ){ if( program > 0 ) { // try containing program program = cbl_label_of(symbol_at(program))->parent; return symbol_match2( program, names, program == 0 ); } } if( yydebug ) { char *ancestry = NULL; const char *sep = ""; for( auto name : names ) { char *partial = ancestry; int asret = asprintf(&ancestry, "%s%s%s", partial? partial : "", sep, name); assert(asret); sep = " -> "; assert(ancestry); free(partial); } if( fields.empty() ) { dbgmsg("%s: '%s' matches no fields", __func__, ancestry); dump_symbol_map2(); } else { char *fieldstr = NULL; sep = ""; for( auto field : fields ) { char *partial = fieldstr; int asret = asprintf(&fieldstr, "%s%s%zu", partial? partial : "", sep, field); assert(asret); sep = ", "; assert(fieldstr); free(partial); } dbgmsg("%s: '%s' matches %zu fields: {%s}", __func__, ancestry, fields.size(), fieldstr); free(fieldstr); } free(ancestry); } return fields; } /* * The names list is in top-down order, front-to-back. This function * iterates backwards over the list, looking for the parent of N at * N-1. */ static symbol_map_t symbol_match( size_t program, std::list<const char *> names ) { auto matched = symbol_match2( program, names ); symbol_map_t output; for( auto isym : matched ) { auto e = symbol_at(isym); auto f = cbl_field_of(e); symbol_map_t::key_type key( e->program, f->name, f->parent ); auto p = symbol_map.find(key); if( p == symbol_map.end() ) { yyerror("%s is not defined", key.name); continue; } auto inserted = output.insert(*p); if( ! inserted.second ) { yyerror("%s is not a unique reference", key.name); } } return output; } static const symbol_elem_t * symbol_field_alias_01; const symbol_elem_t * symbol_field_alias_begin() { return symbol_field_alias_01 = symbol_field_current_record(); } void symbol_field_alias_end() { symbol_field_alias_01 = NULL; } std::pair <symbol_elem_t *, bool> symbol_find( size_t program, std::list<const char *> names ) { symbol_map_t items = symbol_match(program, names); if( symbol_field_alias_01 && items.size() != 1 ) { symbol_map_t qualified; size_t i01( symbol_index(symbol_field_alias_01) ); std::copy_if( items.begin(), items.end(), std::inserter(qualified, qualified.begin()), [i01]( auto item ) { const std::vector<size_t>& ancestors(item.second); return ancestors.back() == i01; } ); items = qualified; } auto unique = items.size() == 1; if( !unique ) { if( items.empty() ) { return std::pair<symbol_elem_t *, bool>(NULL, false); } if( yydebug ) { dbgmsg( "%s:%d: '%s' has %zu possible matches", __func__, __LINE__, names.back(), items.size() ); std::for_each( items.begin(), items.end(), dump_symbol_map_value1 ); } } size_t isym = items.begin()->second.front(); auto output = std::make_pair(symbol_at(isym), unique); assert( FldForward != field_at(isym)->type ); return output; } class in_group { size_t group; public: in_group( size_t group ) : group(group) {} bool operator()( symbol_map_t::const_reference elem ) const { return 0 < std::count( elem.second.begin(), elem.second.end(), this->group ); } }; symbol_elem_t * symbol_find_of( size_t program, std::list<const char *> names, size_t group ) { symbol_map_t input = symbol_match(program, names); if( getenv(__func__) && input.size() != 1 ) { dbgmsg( "%s:%d: '%s' has %zu candidates for group %zu", __func__, __LINE__, names.back(), input.size(), group ); std::for_each( input.begin(), input.end(), dump_symbol_map_value1 ); } symbol_map_t items; std::copy_if( input.begin(), input.end(), std::inserter(items, items.begin()), in_group(group) ); if( items.size() == 1 ) { size_t isym = items.begin()->second.front(); assert( FldForward != field_at(isym)->type ); return symbol_at(isym); } if( yydebug ) { dbgmsg( "%s:%d: '%s' has %zu possible matches", __func__, __LINE__, names.back(), input.size() ); std::for_each( input.begin(), input.end(), dump_symbol_map_value1 ); } return NULL; }