Hello world, this patch is a bit more complicated than originally envisioned.
The problem was that we were not handling external dummy arguments with -fc-prototypes-external. In looking at this, I found that we were not warning about external procedures with different argument lists. This can actually be legal (see the two test cases) but creates a problem for the C prototypes: If we have something like subroutine foo(a,n) external a if (n == 1) call a(1) if (n == 2) call a(2,3) end subroutine foo then, pre-C23, we could just have written out the prototype as void foo_ (void (*a) (), int *n); but this is illegal in C23. What to do? I finally chose to warn about the argument mismatch, with a new option. Warn only because the code above is legal, but include in -Wall because such code seems highly suspect. This option is also implied in -fc-prototypes-external. I also put a warning in the generated header file in that case, so users have a chance to see what is going on (especially since gcc now defaults to C23). Regression-tested. Comments? Suggestions for better wordings? Is -Wall too strong, should this be -Wextra (but then nobody would see it, probably...)? OK for trunk? Best regards Thomas
gcc/fortran/ChangeLog: PR fortran/119049 PR fortran/119074 * dump-parse-tree.cc (seen_conflict): New static varaible. (gfc_dump_external_c_prototypes): Initialize it. If it was set, write out a warning that -std=c23 will not work. (write_proc): Move the work of actually writing out the formal arglist to... (write_formal_arglist): New function. Handle external dummy parameters and their argument lists. If there were mismatched arguments, output an empty argument list in pre-C23 style. * gfortran.h (struct gfc_symbol): Add ext_dummy_arglist_mismatch flag and formal_at. * invoke.texi: Document -Wexternal-argument-mismatch. * lang.opt: Put it in. * resolve.cc (resolve_function): If warning about external argument mismatches, build a formal from actual arglist the first time around, and later compare and warn. (resolve_call): Likewise gcc/testsuite/ChangeLog: PR fortran/119049 PR fortran/119074 * gfortran.dg/interface_55.f90: New test. * gfortran.dg/interface_56.f90: New test.
diff --git a/gcc/fortran/dump-parse-tree.cc b/gcc/fortran/dump-parse-tree.cc index 7726b708ad8..1a15757b57b 100644 --- a/gcc/fortran/dump-parse-tree.cc +++ b/gcc/fortran/dump-parse-tree.cc @@ -4108,6 +4108,8 @@ gfc_dump_c_prototypes (FILE *file) /* Loop over all external symbols, writing out their declarations. */ +static bool seen_conflict; + void gfc_dump_external_c_prototypes (FILE * file) { @@ -4119,6 +4121,7 @@ gfc_dump_external_c_prototypes (FILE * file) return; dumpfile = file; + seen_conflict = false; fprintf (dumpfile, _("/* Prototypes for external procedures generated from %s\n" " by GNU Fortran %s%s.\n\n" @@ -4130,6 +4133,11 @@ gfc_dump_external_c_prototypes (FILE * file) return; gfc_traverse_gsymbol (gfc_gsym_root, show_external_symbol, (void *) &bind_c); + if (seen_conflict) + fprintf (dumpfile, + _("\n\n/* WARNING: Because of differing arguments to an external\n" + " procedure, this header file is not compatible with -std=c23." + "\n\n Use another -std option to compile. */\n")); } /* Callback function for dumping external symbols, be they BIND(C) or @@ -4406,52 +4414,35 @@ write_variable (gfc_symbol *sym) fputs (";\n", dumpfile); } - -/* Write out a procedure, including its arguments. */ static void -write_proc (gfc_symbol *sym, bool bind_c) +write_formal_arglist (gfc_symbol *sym, bool bind_c) { - const char *pre, *type_name, *post; - bool asterisk; - enum type_return rok; gfc_formal_arglist *f; - const char *sym_name; - const char *intent_in; - bool external_character; - - external_character = sym->ts.type == BT_CHARACTER && !bind_c; - - if (sym->binding_label) - sym_name = sym->binding_label; - else - sym_name = sym->name; - - if (sym->ts.type == BT_UNKNOWN || external_character) - { - fprintf (dumpfile, "void "); - fputs (sym_name, dumpfile); - } - else - write_decl (&(sym->ts), sym->as, sym_name, true, &sym->declared_at, bind_c); - - if (!bind_c) - fputs ("_", dumpfile); - fputs (" (", dumpfile); - if (external_character) - { - fprintf (dumpfile, "char *result_%s, size_t result_%s_len", - sym_name, sym_name); - if (sym->formal) - fputs (", ", dumpfile); - } - - for (f = sym->formal; f; f = f->next) + for (f = sym->formal; f != NULL; f = f->next) { + enum type_return rok; + const char *intent_in; gfc_symbol *s; + const char *pre, *type_name, *post; + bool asterisk; + s = f->sym; rok = get_c_type_name (&(s->ts), s->as, &pre, &type_name, &asterisk, &post, false); + /* Procedure arguments have to be converted to function pointers. */ + if (s->attr.subroutine) + { + fprintf (dumpfile, "void (*%s) (", s->name); + if (s->ext_dummy_arglist_mismatch) + seen_conflict = true; + else + write_formal_arglist (s, bind_c); + + fputc (')', dumpfile); + goto next; + } + if (rok == T_ERROR) { gfc_error_now ("Cannot convert %qs to interoperable type at %L", @@ -4461,6 +4452,18 @@ write_proc (gfc_symbol *sym, bool bind_c) return; } + if (s->attr.function) + { + fprintf (dumpfile, "%s (*%s) (", type_name, s->name); + if (s->ext_dummy_arglist_mismatch) + seen_conflict = true; + else + write_formal_arglist (s, bind_c); + + fputc (')',dumpfile); + goto next; + } + /* For explicit arrays, we already set the asterisk above. */ if (!s->attr.value && !(s->as && s->as->type == AS_EXPLICIT)) asterisk = true; @@ -4481,6 +4484,7 @@ write_proc (gfc_symbol *sym, bool bind_c) if (bind_c && rok == T_WARN) fputs(" /* WARNING: non-interoperable KIND */ ", dumpfile); + next: if (f->next) fputs(", ", dumpfile); } @@ -4489,6 +4493,42 @@ write_proc (gfc_symbol *sym, bool bind_c) if (f->sym->ts.type == BT_CHARACTER) fprintf (dumpfile, ", size_t %s_len", f->sym->name); +} + +/* Write out a procedure, including its arguments. */ +static void +write_proc (gfc_symbol *sym, bool bind_c) +{ + const char *sym_name; + bool external_character; + + external_character = sym->ts.type == BT_CHARACTER && !bind_c; + + if (sym->binding_label) + sym_name = sym->binding_label; + else + sym_name = sym->name; + + if (sym->ts.type == BT_UNKNOWN || external_character) + { + fprintf (dumpfile, "void "); + fputs (sym_name, dumpfile); + } + else + write_decl (&(sym->ts), sym->as, sym_name, true, &sym->declared_at, bind_c); + + if (!bind_c) + fputs ("_", dumpfile); + + fputs (" (", dumpfile); + if (external_character) + { + fprintf (dumpfile, "char *result_%s, size_t result_%s_len", + sym_name, sym_name); + if (sym->formal) + fputs (", ", dumpfile); + } + write_formal_arglist (sym, bind_c); fputs (");\n", dumpfile); } diff --git a/gcc/fortran/gfortran.h b/gcc/fortran/gfortran.h index 425454be7b4..927f22cffd1 100644 --- a/gcc/fortran/gfortran.h +++ b/gcc/fortran/gfortran.h @@ -2023,6 +2023,10 @@ typedef struct gfc_symbol scope. Used in the suppression of uninitialized warnings in reallocation on assignment. */ unsigned allocated_in_scope:1; + /* Set if an external dummy argument is called with different argument lists. + This is legal in Fortran, but can cause problems with autogenerated + C prototypes for C23. */ + unsigned ext_dummy_arglist_mismatch; /* Reference counter, used for memory management. @@ -2068,6 +2072,10 @@ typedef struct gfc_symbol /* Link to next entry in derived type list */ struct gfc_symbol *dt_next; + + /* This is for determining where the symbol has been used first, for better + location of error messages. */ + locus formal_at; } gfc_symbol; diff --git a/gcc/fortran/invoke.texi b/gcc/fortran/invoke.texi index 0b50508dd1c..da085d124f9 100644 --- a/gcc/fortran/invoke.texi +++ b/gcc/fortran/invoke.texi @@ -242,6 +242,7 @@ is ignored and no user-defined procedure with the same name as any intrinsic is called except when it is explicitly declared @code{EXTERNAL}. @opindex fallow-argument-mismatch +@cindex argument mismatch @item -fallow-argument-mismatch Some code contains calls to external procedures with mismatches between the calls and the procedure definition, or with mismatches @@ -1068,6 +1069,15 @@ the expression after conversion. Implied by @option{-Wall}. Warn about implicit conversions between different types and kinds. This option does @emph{not} imply @option{-Wconversion}. +@opindex Wexternal-argument-mismatch +@cindex warnings, argument mismatch +@cindex argment mismatch, warnings +@item -Wexternal-argument-mismatch +Warn about argument mismatches for dummy external procedures. This is +implied by @option{-fc-prototypes-external} because generation of a +valid C23 interface is not possible in such a case. Also implied +by @option{-Wall}. + @opindex Wextra @cindex extra warnings @cindex warnings, extra diff --git a/gcc/fortran/lang.opt b/gcc/fortran/lang.opt index 1824c1d953b..7826a1ab5fa 100644 --- a/gcc/fortran/lang.opt +++ b/gcc/fortran/lang.opt @@ -241,6 +241,10 @@ Wdo-subscript Fortran Var(warn_do_subscript) Warning LangEnabledBy(Fortran,Wextra) Warn about possibly incorrect subscripts in do loops. +Wexternal-argument-mismatch +Fortran Var(warn_external_argument_mismatch) Warning LangEnabledBy(Fortran,Wall || fc-prototypes-external) +Warn when arguments of external procedures do not match. + Wextra Fortran Warning ; Documented in common diff --git a/gcc/fortran/resolve.cc b/gcc/fortran/resolve.cc index f83d122a3a2..0773d05bfc6 100644 --- a/gcc/fortran/resolve.cc +++ b/gcc/fortran/resolve.cc @@ -3427,6 +3427,32 @@ resolve_function (gfc_expr *expr) return false; } + /* Add and check formal interface when -fc-prototypes-external is in + force, see comment in resolve_call(). */ + + if (warn_external_argument_mismatch && sym && sym->attr.dummy + && sym->attr.external) + { + if (sym->formal) + { + bool conflict; + conflict = !gfc_compare_actual_formal (&expr->value.function.actual, + sym->formal, 0, 0, 0, NULL); + if (conflict) + { + sym->ext_dummy_arglist_mismatch = 1; + gfc_warning (OPT_Wexternal_argument_mismatch, + "Different argument lists in external dummy " + "function %s at %L and %L", sym->name, + &expr->where, &sym->formal_at); + } + } + else + { + gfc_get_formal_from_actual_arglist (sym, expr->value.function.actual); + sym->formal_at = expr->where; + } + } /* See if function is already resolved. */ if (expr->value.function.name != NULL @@ -3939,6 +3965,43 @@ resolve_call (gfc_code *c) if (csym && is_external_proc (csym)) resolve_global_procedure (csym, &c->loc, 1); + /* If we have an external dummy argument, we want to write out its arguments + with -fc-prototypes-external. Code like + + subroutine foo(a,n) + external a + if (n == 1) call a(1) + if (n == 2) call a(2,3) + end subroutine foo + + is actually legal Fortran, but it is not possible to generate a C23- + compliant prototype for this, so we just record the fact here and + handle that during -fc-prototypes-external processing. */ + + if (warn_external_argument_mismatch && csym && csym->attr.dummy + && csym->attr.external) + { + if (csym->formal) + { + bool conflict; + conflict = !gfc_compare_actual_formal (&c->ext.actual, csym->formal, + 0, 0, 0, NULL); + if (conflict) + { + csym->ext_dummy_arglist_mismatch = 1; + gfc_warning (OPT_Wexternal_argument_mismatch, + "Different argument lists in external dummy " + "subroutine %s at %L and %L", csym->name, + &c->loc, &csym->formal_at); + } + } + else + { + gfc_get_formal_from_actual_arglist (csym, c->ext.actual); + csym->formal_at = c->loc; + } + } + t = true; if (c->resolved_sym == NULL) { diff --git a/gcc/testsuite/gfortran.dg/interface_55.f90 b/gcc/testsuite/gfortran.dg/interface_55.f90 new file mode 100644 index 00000000000..7016a56ed64 --- /dev/null +++ b/gcc/testsuite/gfortran.dg/interface_55.f90 @@ -0,0 +1,26 @@ +! { dg-do run } +! { dg-additional-options "-Wexternal-argument-mismatch" } +! PR 119074 - the code is legal, but it makes sense to warn anyway. + +program main + external ex1,ex2 + call foo(ex1,1) + call foo(ex2,2) +end program main + +subroutine ex1(n) + integer :: n + if (n /= 1) error stop +end subroutine ex1 + +subroutine ex2(n,m) + integer :: n,m + if (n /= 2) error stop + if (m /= 3) error stop +end subroutine ex2 + +subroutine foo(a,n) + external a + if (n == 1) call a(1) ! { dg-warning "Different argument lists" } + if (n == 2) call a(2,3) ! { dg-warning "Different argument lists" } +end subroutine foo diff --git a/gcc/testsuite/gfortran.dg/interface_56.f90 b/gcc/testsuite/gfortran.dg/interface_56.f90 new file mode 100644 index 00000000000..c736c81e9eb --- /dev/null +++ b/gcc/testsuite/gfortran.dg/interface_56.f90 @@ -0,0 +1,32 @@ +! { dg-do run } +! PR 119074 - the code is legal, but it makes sense to warn anyway. +! { dg-additional-options "-Wall" } + +program memain + external i1, i2 + integer i1, i2 + call foo (i1,1) + call foo (i2,2) +end program memain + +integer function i1(n) + i1 = n + 1 +end function i1 + +integer function i2(n,m) + i2 = n + m + 1 +end function i2 + +subroutine foo(f,n) + integer, external :: f + integer :: n + integer :: s + if (n == 1) then + s = f(1) ! { dg-warning "Different argument lists" } + if (s /= 2) error stop + end if + if (n == 2) then + s = f(2,3) ! { dg-warning "Different argument lists" } + if (s /= 6) error stop + end if +end subroutine foo