blob: 423b53a8c34d53e262acba10711b1d1fa3ba7e2e [file] [log] [blame]
/*
* Copyright (c) 2021-2025 Symas Corporation
*
* 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.
*/
/*
* Define a table of diagnositic messages, each uniquely identified and
* grouped into dialects. The user can select on the command line which
* ones are in effect.
*/
#include <cobol-system.h>
#include <coretypes.h>
#include <tree.h>
#undef yy_flex_debug
#include <langinfo.h>
#include <coretypes.h>
#include <version.h>
#include <demangle.h>
#include <intl.h>
#include <backtrace.h>
#include <diagnostic.h>
#include <opts.h>
#include "util.h"
#include "cbldiag.h"
#include "cdfval.h"
#include "lexio.h"
#include "../../libgcobol/ec.h"
#include "../../libgcobol/common-defs.h"
#include "symbols.h"
#include "inspect.h"
#include "../../libgcobol/io.h"
#include "genapi.h"
#include "genutil.h"
#include "../../libgcobol/charmaps.h"
/*
* As of now, every diagnositc has one id, one message, one kind, and is
* associated with "one" dialect. The dialect could be ORed. If it is, that
* means among the dialects it belongs to, it is always of the same kind.
*
* The diagnositic mask in force during compilation may include/exclude
* features based on their associated dialect and/or by id. It may stipulate
* that a warning is treated as an error, too, but that's up the diagnostic
* framework. If a feature requires a dialect and is not specifically enabled,
* gcobol emits of message of the associated kind, and names the dialect
* required.
*/
struct cbl_diag_t {
cbl_diag_id_t id;
cbl_name_t option;
diagnostics::kind kind;
cbl_dialect_t dialect;
explicit cbl_diag_t( cbl_diag_id_t id )
: id(id), option(""), kind(diagnostics::kind::ignored), dialect(dialect_gcc_e)
{}
cbl_diag_t( cbl_diag_id_t id,
const char option[],
diagnostics::kind kind,
cbl_dialect_t dialect = dialect_iso_e )
: id(id), option(""), kind(kind), dialect(dialect)
{
gcc_assert(strlen(option) < sizeof(this->option));
strcpy(this->option, option);
}
bool operator<( const cbl_diag_t& that ) const {
return id < that.id;
}
};
/*
* Initially, errors and warnings are set per the default, dialect gcc. If the
* user chooses dialect iso, all dialect-enabled features are turned into
* errors. If the user selects a more generous dialect, features associated
* with it are set to be ignored.
*
* Individual features may also be suppressed, and all warnings may be elevated
* to errors.
*/
const static auto dialect_mf_gnu = cbl_dialect_t(dialect_mf_e | dialect_gnu_e);
const static auto dialect_ibm_mf_gnu = cbl_dialect_t(dialect_ibm_e |
dialect_mf_e |
dialect_gnu_e);
std::set<cbl_diag_t> cbl_diagnostics {
{ CdfNotFoundW, "-Wcdf-name-not-found", diagnostics::kind::warning },
{ CdfParameterW, "-Wcdf-invalid-parameter", diagnostics::kind::warning },
{ EcUnknownW, "-Wec-unknown", diagnostics::kind::warning },
{ IbmEjectE, "-Wcobol-eject", diagnostics::kind::error, dialect_ibm_e },
{ IbmLengthOf, "-Wlength-of", diagnostics::kind::error, dialect_ibm_mf_gnu },
{ IbmEqualAssignE, "-Wequal-assign", diagnostics::kind::error, dialect_ibm_e },
{ IbmProcedurePointer, "-Wprocedure-pointer", diagnostics::kind::error, dialect_ibm_mf_gnu },
{ IbmSectionNegE, "-Wsegment-negative", diagnostics::kind::error, dialect_ibm_e },
{ IbmSectionRangeE, "-Wsegment-error", diagnostics::kind::error, dialect_ibm_e },
{ IbmSectionSegmentW, "-Wsegment", diagnostics::kind::warning, dialect_ibm_e },
{ IbmStopNumber, "-Wstop-number", diagnostics::kind::error, dialect_ibm_e },
{ IbmVolatileE, "-Wcobol-volatile", diagnostics::kind::error, dialect_ibm_e },
{ IbmVolatileW, "-Wcobol-volatile", diagnostics::kind::warning, dialect_ibm_e },
// RESUME not supported by IBM
{ IsoResume, "-Wcobol-resume", diagnostics::kind::error, dialect_ibm_e },
{ MfBinaryLongLong, "-Wbinary-long-long", diagnostics::kind::error, dialect_mf_gnu },
{ MfCallGiving, "-Wcall-giving", diagnostics::kind::error, dialect_mf_gnu },
{ MfCdfDollar, "-Wcdf-dollar", diagnostics::kind::error, dialect_mf_gnu },
{ MfComp6, "-Wcomp-6", diagnostics::kind::error, dialect_mf_gnu },
{ MfCompX, "-Wcomp-x", diagnostics::kind::error, dialect_mf_gnu },
{ MfLevel_1_Occurs, "Wlevel-1-occurs", diagnostics::kind::error, dialect_mf_gnu },
{ MfLevel78, "-Wlevel-78", diagnostics::kind::error, dialect_mf_gnu },
{ MfMovePointer, "-Wmove-pointer", diagnostics::kind::error, dialect_mf_gnu },
{ MfReturningNum, "-Wreturning-number", diagnostics::kind::error, dialect_mf_gnu },
{ MfUsageTypename, "-Wusage-typename", diagnostics::kind::error, dialect_mf_gnu },
{ MfTrailing, "-Winspect-trailing", diagnostics::kind::error, dialect_mf_gnu },
{ LexIncludeE, "-Winclude-file-not-found", diagnostics::kind::error },
{ LexIncludeOkN, "-Winclude-file-found", diagnostics::kind::note },
{ LexIndicatorE, "-Wstray-indicator", diagnostics::kind::error },
{ LexInputN, "-Wcopybook-found", diagnostics::kind::note },
{ LexLineE, "-Wbad-line-directive", diagnostics::kind::error },
{ LexPreprocessE, "-Wpreprocessor-error", diagnostics::kind::error },
{ LexReplaceE, "-Wreplace-error", diagnostics::kind::error },
// mf and gnu do not require whitespace after relational operators
{ LexSeparatorE, "-Woperator-space", diagnostics::kind::error, dialect_mf_gnu },
{ Par78CdfDefinedW, "-Wlevel-78-defined", diagnostics::kind::warning },
{ ParIconvE, "-Wiconv-error", diagnostics::kind::note },
{ ParInfoI, "-Wentry-convention", diagnostics::kind::note },
{ ParLangInfoW, "-Wnllanginfo-error", diagnostics::kind::warning },
{ ParLiteral2W, "-Wliteral-concat", diagnostics::kind::warning },
{ ParLocaleW, "-Wlocale-error", diagnostics::kind::warning },
{ ParNoCorrespondingW, "-Wmove-corresponding", diagnostics::kind::warning },
{ ParNumstrW, "-Wbad-numeric", diagnostics::kind::warning },
{ ParUnresolvedProcE, "-Wprocedure-not-found", diagnostics::kind::error },
// unimplmeneted syntax warnings
{ SynApplyCommit, "-Wapply-commit", diagnostics::kind::warning },
{ SynFileCodeSet, "-Wfile-code-set", diagnostics::kind::warning },
{ SynHighOrderBit, "-Whigh-order-bit", diagnostics::kind::warning },
{ SynRecordingMode, "-Wrecording-mode", diagnostics::kind::warning },
{ SynSetLocaleTo, "-Wset-locale-to", diagnostics::kind::warning },
{ SynSetToLocale, "-Wset-to-locale", diagnostics::kind::warning },
};
static struct set_verify {
set_verify() {
gcc_assert(cbl_diagnostics.size() == DiagDiagDiag);
auto p = std::find_if(cbl_diagnostics.begin(), cbl_diagnostics.end(),
[]( const auto& diag ) {
return '?' == cbl_dialect_str(diag.dialect)[0];
} );
if( p != cbl_diagnostics.end() ) {
fprintf(stderr, "unregconized dialect '%04x (~%04x)'", p->dialect, ~p->dialect);
}
gcc_assert( std::none_of(cbl_diagnostics.begin(), cbl_diagnostics.end(),
[]( const auto& diag ) {
return '?' == cbl_dialect_str(diag.dialect)[0];
} ) );
}
} verify_consistent_message_count;
static inline diagnostics::kind
kind_of( cbl_diag_id_t id ) {
auto diag = cbl_diagnostics.find(cbl_diag_t(id));
if( diag != cbl_diagnostics.end() ) {
return diag->kind;
}
return diagnostics::kind::ice;
}
diagnostics::kind
cbl_diagnostic_kind( cbl_diag_id_t id ) {
return kind_of(id);
}
bool
cbl_diagnostic_kind( cbl_diag_id_t id, diagnostics::kind kind ) {
auto p = cbl_diagnostics.find( cbl_diag_t{id} );
if( p != cbl_diagnostics.end() ) {
auto diag(*p);
diag.kind = kind;
cbl_diagnostics.erase(p);
return cbl_diagnostics.insert(diag).second;
}
return false;
}
bool
cbl_diagnostic_kind( cbl_dialect_t dialect, diagnostics::kind kind ) {
bool ok = true;
for( auto diag : cbl_diagnostics ) {
if( diag.dialect == dialect ) {
if( ! cbl_diagnostic_kind(diag.id, kind) ) ok = false;
}
}
return ok;
}
void
cobol_warning( cbl_diag_id_t id, int yn, bool warning_as_error ) {
gcc_assert( 0 <= yn && yn <= 1 );
diagnostics::kind kind = yn?
diagnostics::kind::warning : diagnostics::kind::ignored;
if( warning_as_error ) {
kind = diagnostics::kind::error;
}
cbl_diagnostic_kind(id, kind);
}
static inline const char *
option_of( cbl_diag_id_t id ) {
auto diag = cbl_diagnostics.find(cbl_diag_t(id));
if( diag != cbl_diagnostics.end() && diag->option[0] ) {
return diag->option;
}
return nullptr;
}
const char *
cbl_diagnostic_option( cbl_diag_id_t id ) {
return option_of(id);
}
/*
* This is the general message looker-upper. It determines whether the
* diagnositic is in force, at what level, and the message text, and invokes
* the framework.
*/
extern int yychar;
extern YYLTYPE yylloc;
static const diagnostics::option_id option_zero;
location_t current_token_location();
location_t current_token_location(const location_t& loc);
bool
cbl_message( cbl_diag_id_t id, const char gmsgid[], ... ) {
auto_diagnostic_group d;
const char *option;
char *msg = nullptr;
diagnostics::kind kind = kind_of(id);
if( kind == diagnostics::kind::ignored ) return false;
if( (option = option_of(id)) != nullptr ) {
msg = xasprintf("%s [%s]", gmsgid, option);
gmsgid = msg;
}
va_list ap;
va_start (ap, gmsgid);
auto ret = emit_diagnostic_valist( kind, current_token_location(),
option_zero, gmsgid, &ap );
va_end (ap);
free(msg);
return ret;
}
bool cbl_message( cbl_loc_t loc, cbl_diag_id_t id, const char gmsgid[], ... ) {
class temp_loc_t { // copied from util.cc
location_t orig;
public:
temp_loc_t() : orig(current_token_location()) {
if( yychar < 3 ) return;
gcc_location_set(yylloc); // use lookahead location
}
explicit temp_loc_t( const YYLTYPE& loc) : orig(current_token_location()) {
gcc_location_set(loc);
}
explicit temp_loc_t( const YDFLTYPE& loc) : orig(current_token_location()) {
gcc_location_set(loc);
}
~temp_loc_t() {
if( orig != current_token_location() ) {
current_token_location(orig);
}
}
};
auto_diagnostic_group d;
const char *option;
char *msg = nullptr;
diagnostics::kind kind = kind_of(id);
if( kind == diagnostics::kind::ignored ) return false;
if( (option = option_of(id)) != nullptr ) {
msg = xasprintf("%s [%s]", gmsgid, option);
gmsgid = msg;
}
temp_loc_t looker(loc);
va_list ap;
va_start (ap, gmsgid);
rich_location richloc (line_table, current_token_location());
auto ret = emit_diagnostic_valist( kind,
current_token_location(),
option_zero, gmsgid, &ap );
va_end (ap);
free(msg);
return ret;
}
/*
* Verify the dialect associated with the id (and thus term) is covered by the
* dialects currently in effect. If not, issue a standard message of the kind
* defined by the id. Possible combinations:
* dialect required: ok, dialect matches feature dialect
* dialect prohibits not_ok, dialect matches feature ~dialect
*
* If ok is false, then a match means the dialect prohibits the feature.
*/
bool
dialect_ok( const cbl_loc_t& loc, cbl_diag_id_t id, const char term[], bool ok ) {
auto diag = cbl_diagnostics.find(cbl_diag_t(id));
const char *verb = "requires";
if( diag == cbl_diagnostics.end() ) {
gcc_unreachable();
}
if( diag->kind == diagnostics::kind::ignored ) return true;
if( dialect_has(diag->dialect) ) {
if( ok ) {
return true;
} else {
verb = "prohibits";
}
} else {
if( !ok ) return true; // current dialect correctly does not match the feature
}
cbl_message(loc, id, "%qs %s %<-dialect %s%>",
term, verb, cbl_dialect_str(diag->dialect));
return false;
}