aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorXavier Leroy <xavier.leroy@inria.fr>2018-05-03 14:43:54 +0200
committerXavier Leroy <xavierleroy@users.noreply.github.com>2018-05-07 19:19:10 +0200
commitea3a41bcf894511695bf6118390577f4fe609742 (patch)
tree8c8044931f9af7ea894b9b16878766038764f71a
parent2e2d3430436ba1102504ccf175e8bd12c326fc85 (diff)
downloadcompcert-kvx-ea3a41bcf894511695bf6118390577f4fe609742.tar.gz
compcert-kvx-ea3a41bcf894511695bf6118390577f4fe609742.zip
Revised elaboration of function definitions, part 2
Change elab_type_declarator and elab_fundef_name so that the latter returns two environments: the first one takes into account struct/union definitions from the function return type, while the second one also contains bindings for function parameters and struct/union definitions from the function parameter list. To this end the "kr_ok" bool argument of elab_type_declarator is repurposed and renamed "fundef". It controls not just whether K&R function declarators are supported, but also which bindings the returned environment contains. Change elab_fundef to adapt to the changes in elab_fundef_name and to maintain two environments: - the global environment, enriched with struct/union definitions from the function return type, and with the function binding itself; - the local environment, used for elaborating the body of the function, which also contains bindings for function parameters and struct/union definitions from the function parameter list. Change elab_funbody so that it does not open a new scope for elaborating the body, even though the body is represented as a block in the AST. The standard says that the function body is processed in the same scope where function parameters are declared, so that the following is illegal: int f(int x) { double x; ... } Introduce a variant enter_or_refine_function of enter_or_refine_ident where the fresh identifier to use (if no earlier declaration is found) is created in advance in an earlier scope. This helps implement the proper scoping of function names.
-rw-r--r--cparser/Elab.ml208
1 files changed, 143 insertions, 65 deletions
diff --git a/cparser/Elab.ml b/cparser/Elab.ml
index 9245987e..91d88e78 100644
--- a/cparser/Elab.ml
+++ b/cparser/Elab.ml
@@ -188,7 +188,8 @@ let combine_toplevel_definitions loc env s old_sto old_ty sto ty =
in
(new_sto, new_ty)
-let enter_or_refine_ident local loc env s sto ty =
+let enter_or_refine_ident_base local loc env new_id sto ty =
+ let s = new_id.C.name in
(* Check for illegal redefinitions:
- a name that was previously a typedef
- a variable that was already declared in the same local scope
@@ -198,11 +199,11 @@ let enter_or_refine_ident local loc env s sto ty =
if redef Env.lookup_typedef env s then
error loc "redefinition of '%s' as different kind of symbol" s;
begin match previous_def Env.lookup_ident env s with
- | Some(id, Env.II_ident(old_sto, old_ty))
- when local && Env.in_current_scope env id
+ | Some(old_id, Env.II_ident(old_sto, old_ty))
+ when local && Env.in_current_scope env old_id
&& not (sto = Storage_extern && old_sto = Storage_extern) ->
error loc "redefinition of '%s'" s
- | Some(id, Env.II_enum _) when Env.in_current_scope env id ->
+ | Some(old_id, Env.II_enum _) when Env.in_current_scope env old_id ->
error loc "redefinition of '%s' as different kind of symbol" s;
| _ ->
()
@@ -210,22 +211,46 @@ let enter_or_refine_ident local loc env s sto ty =
(* For a block-scoped, non-"extern" variable, a new declaration
is entered, and it has no linkage. *)
if local && sto <> Storage_extern then begin
- let (id, env') = Env.enter_ident env s sto ty in
- (id, sto, env', ty, false)
+ (new_id, sto, Env.add_ident env new_id sto ty, ty, false)
end else begin
(* For a file-scoped or "extern" variable, we need to check against
prior declarations of this variable with internal or external linkage.
The variable has linkage. *)
match previous_def Env.lookup_ident !top_environment s with
- | Some(id, Env.II_ident(old_sto, old_ty)) ->
+ | Some(old_id, Env.II_ident(old_sto, old_ty)) ->
let (new_sto, new_ty) =
combine_toplevel_definitions loc env s old_sto old_ty sto ty in
- (id, new_sto, Env.add_ident env id new_sto new_ty, new_ty, true)
+ (old_id, new_sto, Env.add_ident env old_id new_sto new_ty, new_ty, true)
| _ ->
- let (id, env') = Env.enter_ident env s sto ty in
- (id, sto, env', ty, true)
+ (new_id, sto, Env.add_ident env new_id sto ty, ty, true)
end
+(* We use two variants of [enter_or_refine]:
+
+ - [enter_or_refine_ident] is to be used for all declarations,
+ block-scoped ([local = true]) or top-level ([local = false]).
+ The name of the declared thing is given as a string [s]. If a
+ previous declaration with this name exists in the current scope,
+ its unique identifier is returned. Otherwise a fresh identifier
+ named [s] is created in the current scope of [env] and returned.
+
+ - [enter_or_refine_function] is to be used for function definitions.
+ Such definitions are always global, hence the [local] parameter
+ defaults to [false] and is omitted. The function name is given
+ as an identifier, created in advance by the caller. This
+ identifier is used if no previous declaration exists for the
+ function. Otherwise, the identifier of the previous declaration is
+ used. By creating the identifier in advance, [elab_fundef] makes
+ sure that it is properly scoped to file scope and not to the local
+ scope of function parameters.
+*)
+
+let enter_or_refine_ident local loc env s sto ty =
+ enter_or_refine_ident_base local loc env (Env.fresh_ident s) sto ty
+
+let enter_or_refine_function loc env id sto ty =
+ enter_or_refine_ident_base false loc env id sto ty
+
(* Forward declarations *)
let elab_expr_f : (cabsloc -> Env.t -> Cabs.expression -> C.exp * Env.t) ref
@@ -688,7 +713,14 @@ and elab_return_type loc env ty =
error loc "function cannot return function type %a" (print_typ env) ty
| _ -> ()
-and elab_type_declarator loc env ty kr_ok = function
+(* The [?fundef] parameter is true if we're elaborating a function definition
+ and false otherwise. When [fundef = true], K&R function declarators
+ are allowed, and the returned environment includes bindings for the
+ function parameters and the struct/unions they may define.
+ When [fundef = false], K&R function declarators are rejected
+ and declarations in parameters are not returned. *)
+
+and elab_type_declarator ?(fundef = false) loc env ty = function
| Cabs.JUSTBASE ->
((ty, None), env)
| Cabs.ARRAY(d, cv_specs, sz) ->
@@ -712,31 +744,37 @@ and elab_type_declarator loc env ty kr_ok = function
| None ->
error loc "size of array is not a compile-time constant";
Some 1L in (* produces better error messages later *)
- elab_type_declarator loc env (TArray(ty, sz', a)) kr_ok d
+ elab_type_declarator ~fundef loc env (TArray(ty, sz', a)) d
| Cabs.PTR(cv_specs, d) ->
let (ty, a) = get_nontype_attrs env ty in
let a = add_attributes a (elab_cvspecs env cv_specs) in
- elab_type_declarator loc env (TPtr(ty, a)) kr_ok d
+ elab_type_declarator ~fundef loc env (TPtr(ty, a)) d
| Cabs.PROTO(d, (params, vararg)) ->
elab_return_type loc env ty;
let (ty, a) = get_nontype_attrs env ty in
- let (params', _) = elab_parameters env params in
- (* Temporary: we discard the extended environment returned by
- elab_parameters, on the assumption that this is the end of
- the scope introduced to treat the parameters. This is a
- correct assumption if we're in a function declaration.
- For a function definition, more work is needed and will come later. *)
- elab_type_declarator loc env (TFun(ty, Some params', vararg, a)) kr_ok d
+ let (params', env') = elab_parameters env params in
+ (* For a function declaration (fundef = false), the scope introduced
+ to treat parameters ends here, so we discard the extended
+ environment env' returned by elab_parameters.
+ For a function definition (fundef = true) we return the
+ extended environment env' so that it can serve as the basis
+ to elaborating the function body. *)
+ let env'' = if fundef then env' else env in
+ elab_type_declarator ~fundef loc env'' (TFun(ty, Some params', vararg, a)) d
| Cabs.PROTO_OLD(d, params) ->
elab_return_type loc env ty;
let (ty, a) = get_nontype_attrs env ty in
+ (* For consistency with the PROTO case above, for a function definition
+ (fundef = true) we open a new scope, even though we do not
+ add any bindings for the parameters. *)
+ let env'' = if fundef then Env.new_scope env else env in
match params with
| [] ->
- elab_type_declarator loc env (TFun(ty, None, false, a)) kr_ok d
+ elab_type_declarator ~fundef loc env'' (TFun(ty, None, false, a)) d
| _ ->
- if not kr_ok || d <> Cabs.JUSTBASE then
+ if not fundef || d <> Cabs.JUSTBASE then
fatal_error loc "illegal old-style K&R function definition";
- ((TFun(ty, None, false, a), Some params), env)
+ ((TFun(ty, None, false, a), Some params), env'')
(* Elaboration of parameters in a prototype *)
@@ -754,7 +792,7 @@ and elab_parameter env (PARAM (spec, id, decl, attr, loc)) =
let (sto, inl, noret, tydef, bty, env1) = elab_specifier loc env spec in
if tydef then
error loc "'typedef' used in function parameter";
- let ((ty, _), _) = elab_type_declarator loc env1 bty false decl in
+ let ((ty, _), _) = elab_type_declarator loc env1 bty decl in
let ty = add_attributes_type (elab_attributes env attr) ty in
if sto <> Storage_default && sto <> Storage_register then
error loc (* NB: 'auto' not allowed *)
@@ -774,15 +812,24 @@ and elab_parameter env (PARAM (spec, id, decl, attr, loc)) =
let (id', env2) = Env.enter_ident env1 id sto ty1 in
( (id', ty1) , env2)
-(* Elaboration of a (specifier, Cabs "name") pair *)
+(* Elaboration of a (specifier, Cabs "name") pair as found in a function
+ definition. Returns two environments: the first is [env]
+ enriched with struct/union definitions from the return type,
+ as usual; the second is like the first, plus a new scope.
+ For a prototyped function ([kr_params = None]) the new scope
+ includes bindings for the function parameters and the struct/unions
+ they may define. For a K&R function ([kr_params <> None]) the new
+ scope is empty. *)
-and elab_fundef_name env spec (Name (id, decl, attr, loc)) =
+and elab_fundef_name env spec (Name (s, decl, attr, loc)) =
let (sto, inl, noret, tydef, bty, env') = elab_specifier loc env spec in
if tydef then
error loc "'typedef' is forbidden here";
- let ((ty, kr_params), env'') = elab_type_declarator loc env' bty true decl in
+ let id = Env.fresh_ident s in
+ let ((ty, kr_params), env'') =
+ elab_type_declarator ~fundef:true loc env' bty decl in
let a = elab_attributes env attr in
- (id, sto, inl, noret, add_attributes_type a ty, kr_params, env'')
+ (id, sto, inl, noret, add_attributes_type a ty, kr_params, env', env'')
(* Elaboration of a name group. C99 section 6.7.6 *)
@@ -797,7 +844,7 @@ and elab_name_group loc env (spec, namelist) =
error loc "'_Noreturn' is forbidden here";
let elab_one_name env (Name (id, decl, attr, loc)) =
let ((ty, _), env1) =
- elab_type_declarator loc env bty false decl in
+ elab_type_declarator loc env bty decl in
let a = elab_attributes env attr in
((id, add_attributes_type a ty), env1) in
(mmap elab_one_name env' namelist, sto)
@@ -811,7 +858,7 @@ and elab_init_name_group loc env (spec, namelist) =
error loc "'_Noreturn' can only appear on functions";
let elab_one_name env (Init_name (Name (id, decl, attr, loc), init)) =
let ((ty, _), env1) =
- elab_type_declarator loc env bty false decl in
+ elab_type_declarator loc env bty decl in
let a = elab_attributes env attr in
if inl && not (is_function_type env ty) then
error loc "'inline' can only appear on functions";
@@ -1061,7 +1108,7 @@ and elab_enum only loc tag optmembers attrs env =
let elab_type loc env spec decl =
let (sto, inl, noret, tydef, bty, env') = elab_specifier loc env spec in
- let ((ty, _), env'') = elab_type_declarator loc env' bty false decl in
+ let ((ty, _), env'') = elab_type_declarator loc env' bty decl in
(* NB: it seems the parser doesn't accept any of the specifiers below.
We keep the error message as extra safety. *)
if sto <> Storage_default || inl || noret || tydef then
@@ -2388,9 +2435,19 @@ let inherit_vararg env s sto ty =
(* Function definitions *)
-let elab_fundef env spec name defs body loc =
- let (s, sto, inline, noret, ty, kr_params, env1) =
- elab_fundef_name env spec name in
+let elab_fundef genv spec name defs body loc =
+ (* We maintain two environments:
+ - genv is the "global", file-scope environment. It is enriched
+ with the declaration of the function, and also with
+ structs and unions defined as part of its return type.
+ - lenv is the "local" environment used to elaborate the function body.
+ It contains everything that genv contains, including
+ a declaration for the function, so as to support recursive calls.
+ In addition, it contains declarations for function parameters
+ and structs and unions defined in the parameter list. *)
+ let (fun_id, sto, inline, noret, ty, kr_params, genv, lenv) =
+ elab_fundef_name genv spec name in
+ let s = fun_id.C.name in
if sto = Storage_auto || sto = Storage_register then
fatal_error loc "invalid storage class %s on function"
(name_of_storage_class sto);
@@ -2403,57 +2460,70 @@ let elab_fundef env spec name defs body loc =
(* Process the parameters and the K&R declarations, if any, to obtain:
- [ty]: the full, prototyped type of the function
- [extra_decls]: extra declarations to be inserted at the
- beginning of the function *)
- let (ty, extra_decls,env1) =
+ beginning of the function
+ - [lenv]: a first cut at the local environment, containing in particular
+ the structs and unions defined in the parameter list. *)
+ let (ty, extra_decls, lenv) =
match ty, kr_params with
| TFun(ty_ret, None, vararg, attr), None ->
- (TFun(ty_ret, Some [], vararg, attr), [],env1)
+ (TFun(ty_ret, Some [], vararg, attr), [], lenv)
| ty, None ->
- (ty, [],env1)
+ (ty, [], lenv)
| TFun(ty_ret, None, false, attr), Some params ->
warning loc CompCert_conformance "non-prototype, pre-standard function definition, converting to prototype form";
- let (params', extra_decls,env) =
- elab_KR_function_parameters env params defs loc in
- (TFun(ty_ret, Some params', inherit_vararg env s sto ty, attr), extra_decls,env)
+ let (params', extra_decls, lenv) =
+ elab_KR_function_parameters lenv params defs loc in
+ (TFun(ty_ret, Some params', inherit_vararg genv s sto ty, attr), extra_decls, lenv)
| _, Some params ->
assert false
in
- (* Extract infos from the type of the function *)
+ (* Extract infos from the type of the function.
+ Checks on the return type must be done in the global environment. *)
let (ty_ret, params, vararg, attr) =
match ty with
| TFun(ty_ret, Some params, vararg, attr) ->
- if wrap incomplete_type loc env1 ty_ret && not (is_void_type env ty_ret) then
+ if wrap incomplete_type loc genv ty_ret && not (is_void_type genv ty_ret) then
fatal_error loc "incomplete result type %a in function definition"
- (print_typ env) ty_ret;
+ (print_typ genv) ty_ret;
(ty_ret, params, vararg, attr)
| _ -> fatal_error loc "wrong type for function definition" in
- (* Enter function in the environment, for recursive references *)
- let (fun_id, sto1, env1, new_ty, _) =
- enter_or_refine_ident false loc env1 s sto ty in
+ (* Enter function in the global environment *)
+ let (fun_id, sto1, genv, new_ty, _) =
+ enter_or_refine_function loc genv fun_id sto ty in
add_global_define loc s;
+ (* Also enter it in the local environment, for recursive references *)
+ let lenv = Env.add_ident lenv fun_id sto new_ty in
(* Take into account attributes from previous declarations of the function *)
- let attr = attributes_of_type env1 new_ty in
+ let attr = attributes_of_type genv new_ty in
+ (* Additional checks on function parameters *)
let incomplete_param env ty =
if wrap incomplete_type loc env ty then
fatal_error loc "parameter has incomplete type" in
- (* Enter parameters and extra declarations in the environment *)
- let env2 =
+ (* Enter parameters and extra declarations in the local environment.
+ For K&R functions this hasn't been done yet.
+ For prototyped functions this has been done by [elab_fundef_name]
+ already, but some parameter may have been shadowed by the
+ function name, while it should be the other way around, e.g.
+ [int f(int f) { return f+1; }], with [f] refering to the
+ parameter [f] and not to the function [f] within the body of the
+ function. *)
+ let lenv =
List.fold_left (fun e (id, ty) -> incomplete_param e ty;
Env.add_ident e id Storage_default ty)
- (Env.new_scope env1) params in
- let env2 =
+ lenv params in
+ let lenv =
List.fold_left (fun e (sto, id, ty, init) -> Env.add_ident e id sto ty)
- env2 extra_decls in
- (* Define "__func__" and enter it in the environment *)
+ lenv extra_decls in
+ (* Define "__func__" and enter it in the local environment *)
let (func_ty, func_init) = __func__type_and_init s in
- let (func_id, _, env3, func_ty, _) =
- enter_or_refine_ident true loc env2 "__func__" Storage_static func_ty in
- emit_elab ~debuginfo:false env3 loc
+ let (func_id, _, lenv, func_ty, _) =
+ enter_or_refine_ident true loc lenv "__func__" Storage_static func_ty in
+ emit_elab ~debuginfo:false lenv loc
(Gdecl(Storage_static, func_id, func_ty, Some func_init));
(* Elaborate function body *)
- let body1 = !elab_funbody_f ty_ret vararg env3 body in
+ let body1 = !elab_funbody_f ty_ret vararg lenv body in
(* Analyse it for returns *)
- let (can_return, can_fallthrough) = Cflow.function_returns env3 body1 in
+ let (can_return, can_fallthrough) = Cflow.function_returns lenv body1 in
(* Special treatment of the "main" function. *)
let body2 =
if s = "main" then begin
@@ -2461,7 +2531,7 @@ let elab_fundef env spec name defs body loc =
error loc "'main' is not allowed to be declared inline";
if noret then
warning loc Unnamed "'main' is not allowed to be declared _Noreturn";
- match unroll env ty_ret with
+ match unroll genv ty_ret with
| TInt(IInt, []) ->
(* Add implicit "return 0;" at end of function body.
If we trusted the return analysis, we would do this only if
@@ -2474,7 +2544,7 @@ let elab_fundef env spec name defs body loc =
end else begin
(* For non-"main" functions, warn if the body can fall through
and the return type is not "void". *)
- if can_fallthrough && not (is_void_type env ty_ret) then
+ if can_fallthrough && not (is_void_type genv ty_ret) then
warning loc Return_type "control reaches end of non-void function";
body1
end in
@@ -2503,8 +2573,8 @@ let elab_fundef env spec name defs body loc =
fd_vararg = vararg;
fd_locals = [];
fd_body = body3 } in
- emit_elab ~linkage:true env1 loc (Gfundef fn);
- env1
+ emit_elab ~linkage:true genv loc (Gfundef fn);
+ genv
(* Definitions *)
@@ -2832,8 +2902,16 @@ let elab_funbody return_typ vararg env b =
ctx_break = false;
ctx_continue = false;
ctx_in_switch = false;
- ctx_vararg = vararg;} in
- fst(elab_stmt env ctx b)
+ ctx_vararg = vararg } in
+ (* The function body appears as a block in the AST but should not create
+ a new scope. Instead, the scope used for function parameters must be
+ used for the body. *)
+ match b with
+ | BLOCK (b,loc) ->
+ let b',_ = elab_block_body env ctx b in
+ { sdesc = Sblock b'; sloc = elab_loc loc }
+ | _ ->
+ assert false
(* Filling in forward declaration *)
let _ = elab_funbody_f := elab_funbody