diff --git a/src/kernel_internals/typing/populate_spec.ml b/src/kernel_internals/typing/populate_spec.ml
index bbbbd7b7fc2f0d5e6df9c55181c2114865da1167..490a6b7c241327e0480386c85cdf25d3528a9bd3 100644
--- a/src/kernel_internals/typing/populate_spec.ml
+++ b/src/kernel_internals/typing/populate_spec.ml
@@ -101,6 +101,8 @@ sig
   type clause
   type behaviors
 
+  val name : string
+  val is_empty : clause -> bool
   val has_behavior : string -> behaviors -> bool
   val collect_behaviors : spec -> behaviors
   val completes : string list list -> behaviors -> clause list option
@@ -118,25 +120,41 @@ end
 module Make(G : Generator) =
 struct
 
-  let get_default mode kf spec =
-    let table = G.collect_behaviors spec in
-    if mode = Skip || G.has_behavior default table then
-      Kept
-    else if mode = ACSL then
-      Generated (G.acsl_default ())
+  let combine_or_default mode kf spec table =
+    if mode = ACSL then false, G.acsl_default ()
     else
       let completes_opt = G.completes spec.spec_complete_behaviors table in
       match mode, completes_opt with
       | (Safe | Frama_C | Other _), Some completes_clauses ->
-        Generated (G.combine_default completes_clauses)
+        true, G.combine_default completes_clauses
       | Safe, None ->
-        Generated(G.safe_default @@ Kernel_function.has_definition kf)
+        false, G.safe_default @@ Kernel_function.has_definition kf
       | Frama_C, None ->
-        Generated(G.frama_c_default kf)
+        false, G.frama_c_default kf
       | Other mode, None ->
-        Generated(G.custom_default mode kf spec)
+        false, G.custom_default mode kf spec
       | (Skip | ACSL), _ -> assert false
 
+  let get_default ~warned mode kf spec =
+    let table = G.collect_behaviors spec in
+    if mode = Skip || G.has_behavior default table then Kept
+    else
+      let combined, g = combine_or_default mode kf spec table in
+      if not @@ Kernel_function.has_definition kf then begin
+        if combined then
+          Kernel.warning ~once:true ~current:true ~wkey:Kernel.wkey_missing_spec
+            "Missing %s in default behavior of prototype %a,@, \
+             generating default specification from complete behaviors"
+            G.name Kernel_function.pretty kf
+        else if not warned && not @@ G.is_empty g then
+          Kernel.warning ~once:true ~current:true ~wkey:Kernel.wkey_missing_spec
+            "Missing %s in specification of prototype %a,@, \
+             generating default specification, see -generated-spec-* options\
+             for more info"
+            G.name Kernel_function.pretty kf
+      end;
+      Generated g
+
   let emit = G.emit
 
 end
@@ -147,6 +165,10 @@ struct
   type clause = exits
   type behaviors = (string, clause) Hashtbl.t
 
+  let name = "exits"
+
+  let is_empty c = c = []
+
   let has_behavior name behaviors =
     Hashtbl.mem behaviors name
 
@@ -221,6 +243,10 @@ struct
   type clause = assigns
   type behaviors = (string, assigns) Hashtbl.t
 
+  let name = "assigns"
+
+  let is_empty c = c = WritesAny
+
   let has_behavior name behaviors =
     Hashtbl.mem behaviors name
 
@@ -329,6 +355,10 @@ struct
   type clause = requires
   type behaviors = (string, clause) Hashtbl.t
 
+  let name = "requires"
+
+  let is_empty c = c = []
+
   let has_behavior name behaviors =
     Hashtbl.mem behaviors name
 
@@ -383,6 +413,10 @@ struct
   type clause = allocation
   type behaviors = (string, allocation) Hashtbl.t
 
+  let name = "allocates"
+
+  let is_empty c = c = FreeAllocAny
+
   let has_behavior name behaviors =
     Hashtbl.mem behaviors name
 
@@ -465,6 +499,10 @@ struct
   type clause = terminates
   type behaviors = bool
 
+  let name = "terminates"
+
+  let is_empty c = c = None
+
   let has_behavior _ behaviors =
     behaviors
 
@@ -564,19 +602,22 @@ let get_config () =
     | "terminates" -> {conf with terminates = mode}
     | s ->
       Kernel.abort
-        "@[@['%s'@] is not a valid key for -generated-spec-custom.@ Accepted \
+        "@['%s'@] is not a valid key for -generated-spec-custom.@, Accepted \
          keys are 'exits', 'assigns', 'requires', 'allocates' and \
-         'terminates'.@]@." s
+         'terminates'." s
   in
   Kernel.GeneratedSpecCustom.fold collect default
 
-let do_populate kf original_spec =
+let do_populate ~warned kf original_spec =
   let config = get_config () in
-  let exits = Exits.get_default config.exits kf original_spec in
-  let assigns = Assigns.get_default config.assigns kf original_spec in
-  let requires = Requires.get_default config.requires kf original_spec in
-  let allocates = Allocates.get_default config.allocates kf original_spec in
-  let terminates = Terminates.get_default config.terminates kf original_spec in
+  let apply get_default mode =
+    get_default ~warned mode kf original_spec
+  in
+  let exits = apply Exits.get_default config.exits in
+  let assigns = apply Assigns.get_default config.assigns in
+  let requires = apply Requires.get_default config.requires in
+  let allocates = apply Allocates.get_default config.allocates in
+  let terminates = apply Terminates.get_default config.terminates in
 
   let generated original = function
     | Kept -> original
@@ -612,6 +653,13 @@ module Is_populated =
 
 let () = Ast.add_linked_state Is_populated.self
 
+let warn_empty kf =
+  Kernel.warning ~once:true ~current:true ~wkey:Kernel.wkey_missing_spec
+    "Neither code nor specification for function %a,@, \
+     generating default specifications from the prototype"
+    Kernel_function.pretty kf;
+  true
+
 let populate_funspec ~force kf spec =
   let is_proto = not @@ Kernel_function.has_definition kf in
   let skip_generation = not @@ Kernel.GenerateDefaultSpec.get () in
@@ -620,12 +668,8 @@ let populate_funspec ~force kf spec =
   if (not force && skip_generation) || Is_populated.mem kf || skip_proto
   then false
   else begin
-    if is_proto && is_empty_spec then
-      Kernel.warning ~once:true ~current:true ~wkey:Kernel.wkey_missing_spec
-        "Neither code nor specification for function %a, \
-        generating default specs from the prototype"
-        Kernel_function.pretty kf;
-    do_populate kf spec;
+    let warned = if is_proto && is_empty_spec then warn_empty kf else false in
+    do_populate ~warned kf spec;
     Is_populated.add kf ();
     true
   end
diff --git a/src/kernel_internals/typing/populate_spec.mli b/src/kernel_internals/typing/populate_spec.mli
index 27ae84f04b666934a5b768c68b53b05fda3b1ea2..946261bee28f9213c723ec7532d7ae30462d054b 100644
--- a/src/kernel_internals/typing/populate_spec.mli
+++ b/src/kernel_internals/typing/populate_spec.mli
@@ -22,10 +22,6 @@
 
 open Cil_types
 
-type mode = ACSL | Safe | Frama_C | Skip | Other of string
-
-type 'a result = Kept | Generated of 'a
-
 type exits = (termination_kind * identified_predicate) list
 type requires = identified_predicate list
 type terminates = identified_predicate option
@@ -33,25 +29,6 @@ type terminates = identified_predicate option
 type 'a gen = (kernel_function -> spec -> 'a)
 type status = Property_status.emitted_status
 
-module type Generator =
-sig
-
-  type clause
-  type behaviors
-
-  val has_behavior : string -> behaviors -> bool
-  val collect_behaviors : spec -> behaviors
-  val completes : string list list -> behaviors -> clause list option
-
-  val acsl_default : unit -> clause
-  val safe_default : bool -> clause
-  val frama_c_default : kernel_function -> clause
-  val combine_default : clause list -> clause
-  val custom_default : string -> kernel_function -> spec -> clause
-
-  val emit : mode -> kernel_function -> funbehavior -> clause result -> unit
-end
-
 val register :
   ?gen_exits:exits gen -> ?status_exits:status ->
   ?gen_assigns:assigns gen -> ?status_assigns:status ->