diff: nested modules: check for import of inaccessible modules

Fergus Henderson fjh at cs.mu.OZ.AU
Thu Mar 19 04:29:50 AEDT 1998


Estimated hours taken: 4

Fix a limitation of the current nested module support:
ensure that we check for attempts to import inaccessible modules.

compiler/modules.m:
	Check for attempts to import inaccessible modules
	(modules whose parent has not been imported,
	or for which there is no `include_module'
	declaration in the parent's interface).
	Also export make_pseudo_decl/2, for use by intermod.m.

compiler/intermod.m:
	Modify the way we import `.opt' files.  The previous method
	temporarily set the items field of the module_imports
	structure to [], but check_module_accessibility relies
	on the assumption that the items field contains the
	items for all modules previously read in.

tests/invalid/Mmakefile:
tests/invalid/test_nested.m:
tests/invalid/test_nested.err_exp:
tests/invalid/parent.m:
tests/invalid/parent.private_child.m:
tests/invalid/parent.public_child.m:
tests/invalid/parent.undeclared_child.m:
tests/invalid/parent.undeclared_child.err_exp:
tests/invalid/parent2.m:
tests/invalid/parent2.child.m:
	Add some tests for the above change.

doc/reference_manual.texi:
	Update the "bugs and limitations" sub-section of the modules
	chapter to reflect the new status quo.

cvs diff -N compiler/intermod.m compiler/modules.m doc/reference_manual.texi tests/invalid/Mmakefile tests/invalid/parent.m tests/invalid/parent.private_child.m tests/invalid/parent.public_child.m tests/invalid/parent.undeclared_child.err_exp tests/invalid/parent.undeclared_child.m tests/invalid/parent2.child.m tests/invalid/parent2.m tests/invalid/test_nested.err_exp tests/invalid/test_nested.m
Index: compiler/intermod.m
===================================================================
RCS file: /home/mercury1/repository/mercury/compiler/intermod.m,v
retrieving revision 1.48
diff -u -r1.48 intermod.m
--- intermod.m	1998/03/18 08:07:36	1.48
+++ intermod.m	1998/03/18 16:27:13
@@ -1234,20 +1234,29 @@
 	% Read in and process the optimization interfaces.
 
 intermod__grab_optfiles(Module0, Module, FoundError) -->
-		%
-		% Let make_hlds know the opt_imported stuff is coming.
-		%
-	{ append_pseudo_decl(Module0, opt_imported, Module1) },
-	{ module_imports_get_items(Module1, Items0) },
 
 		%
 		% Read in the .opt files for imported and ancestor modules.
 		%
-	{ Module1 = module_imports(ModuleName, Ancestors0, InterfaceDeps0,
+	{ Module0 = module_imports(ModuleName, Ancestors0, InterfaceDeps0,
 					ImplementationDeps0, _, _, _, _, _) },
 	{ list__condense([Ancestors0, InterfaceDeps0, ImplementationDeps0],
 		OptFiles) },
 	read_optimization_interfaces(OptFiles, [], OptItems, no, OptError),
+
+		%
+		% Append the items to the current item list, using
+		% a `opt_imported' psuedo-declaration to let
+		% make_hlds know the opt_imported stuff is coming.
+		%
+	{ module_imports_get_items(Module0, Items0) },
+	{ make_pseudo_decl(opt_imported, OptImportedDecl) },
+	{ list__append(Items0, [OptImportedDecl | OptItems], Items1) },
+	{ module_imports_set_items(Module0, Items1, Module1) },
+
+		%
+		% Figure out which .int files are needed by the .opt files
+		%
 	{ get_dependencies(OptItems, NewImportDeps0, NewUseDeps0) },
 	{ list__append(NewImportDeps0, NewUseDeps0, NewDeps0) },
 	{ set__list_to_set(NewDeps0, NewDepsSet0) },
@@ -1258,12 +1267,10 @@
 		% Read in the .int, and .int2 files needed by the .opt files.
 		% (XXX do we also need to read in .int0 files here?)
 		%
-	{ module_imports_set_items(Module1, [], Module2) },
 	process_module_long_interfaces(NewDeps, ".int", [], NewIndirectDeps,
-				Module2, Module3),
+				Module1, Module2),
 	process_module_indirect_imports(NewIndirectDeps, ".int2",
-				Module3, Module4),
-	{ module_imports_get_items(Module4, InterfaceItems) },
+				Module2, Module3),
 
 		%
 		% Get the :- pragma unused_args(...) declarations created
@@ -1280,28 +1287,25 @@
 					Item = pragma(PragmaType) - _,
 					PragmaType = unused_args(_,_,_,_,_)
 				)) },
-		{ list__filter(IsPragmaUnusedArgs, LocalItems, PragmaItems) }
+		{ list__filter(IsPragmaUnusedArgs, LocalItems, PragmaItems) },
+
+		{ module_imports_get_items(Module3, Items3) },
+		{ list__append(Items3, PragmaItems, Items) },
+		{ module_imports_set_items(Module3, Items, Module) }
 	;
-		{ PragmaItems = [] },
+		{ Module = Module3 },
 		{ UAError = no }
 	),
 
 		%
 		% Figure out whether anything went wrong
 		%
-	{ module_imports_get_error(Module4, FoundError0) },
+	{ module_imports_get_error(Module, FoundError0) },
 	{ ( FoundError0 \= no ; OptError = yes ; UAError = yes) ->
 		FoundError = yes
 	;
 		FoundError = no
-	},
-
-		%
-		% Concatenate everything together.
-		%
-	{ list__condense([Items0, PragmaItems, OptItems, InterfaceItems],
-		Items) },
-	{ module_imports_set_items(Module4, Items, Module) }.
+	}.
 
 :- pred read_optimization_interfaces(list(module_name)::in, item_list::in,
 			item_list::out, bool::in, bool::out,
Index: compiler/modules.m
===================================================================
RCS file: /home/mercury1/repository/mercury/compiler/modules.m,v
retrieving revision 1.63
diff -u -r1.63 modules.m
--- modules.m	1998/03/18 08:07:43	1.63
+++ modules.m	1998/03/18 16:28:09
@@ -194,6 +194,13 @@
 				module_imports).
 :- mode module_imports_set_indirect_deps(in, in, out) is det.
 
+	% make an item_and_context for a module declaration
+	% or pseudo-declaration such as `:- imported'
+	% (which is inserted by the compiler, but can't be used
+	% in user code).
+:- pred make_pseudo_decl(module_defn, item_and_context).
+:- mode make_pseudo_decl(in, out) is det.
+
 	% append_pseudo_decl(Module0, PseudoDecl, Module):
 	%	append the specified module declaration to the list
 	%	of items in Module0 to give Module.
@@ -1022,8 +1029,6 @@
 				IndirectDeps, PublicChildren, FactDeps,
 				Items, Error).
 
-:- pred make_pseudo_decl(module_defn, item_and_context).
-:- mode make_pseudo_decl(in, out) is det.
 make_pseudo_decl(PseudoDecl, Item) :-
 	term__context_init(Context),
 	varset__init(Varset),
@@ -2476,7 +2481,9 @@
 			{ ModImplementationImports = ModImplementationImports0 }
 		;
 			{ ModImplementationImports =
-				[Import | ModImplementationImports0] }
+				[Import | ModImplementationImports0] },
+			check_module_accessibility(ModuleName, Import,
+				ModItems0)
 		),
 		{ get_dependencies(Items, IndirectImports1, IndirectUses1) },
 		{ list__append(IndirectImports0, IndirectImports1,
@@ -2492,6 +2499,114 @@
 		process_module_long_interfaces(Imports, Ext,
 			IndirectImports3, IndirectImports, Module1, Module)
 	).
+
+:- pred check_module_accessibility(module_name, module_name, item_list,
+				io__state, io__state).
+:- mode check_module_accessibility(in, in, in, di, uo) is det.
+
+check_module_accessibility(ModuleName, ImportedModule, Items) -->
+	( { ImportedModule = qualified(ParentModule, SubModule) } ->
+		%
+		% Check that the imported/used module is accessible,
+		% by searching through the current item list (we should
+		% have already read in the imported module's parent module
+		% at this point, so the item list should include the items
+		% in the parent's interface) looking for an `include_module'
+		% declaration that names it.
+		%
+		(
+			{ get_children(Items, AccessibleSubModules) },
+			{ list__member(ImportedModule, AccessibleSubModules) }
+		->
+			[]
+		;
+			% The user attempted to import an inaccessible
+			% sub-module, so report an error.
+			% Unfortunately we didn't get passed the
+			% context of the `import_module' or `use_module'
+			% declaration(s), so we need to search the item
+			% list again to find them.
+			{ FindImports = lambda([Item::in] is semidet, (
+				Item = module_defn(_, ModuleDefn) - _,
+				( ModuleDefn = import(module(Mods))
+				; ModuleDefn = use(module(Mods))
+				),
+				list__member(ImportedModule, Mods)
+			  )) },
+			{ list__filter(FindImports, Items, ImportItems) },
+			{ ImportItems = [] ->
+				error("check_parent_module")
+			;
+				true
+			},
+			list__foldl(report_inaccessible_module_error(
+				ModuleName, ParentModule, SubModule),
+				ImportItems)
+		)
+	;
+		[]
+	).
+
+:- pred report_inaccessible_module_error(module_name, module_name, string,
+			item_and_context, io__state, io__state).
+:- mode report_inaccessible_module_error(in, in, in, in, di, uo) is det.
+
+/*
+The error message should come out like this
+(the second sentence is included only with --verbose-errors):
+very_long_name.m:123: In module `very_long_name':
+very_long_name.m:123:   error in `import_module' declaration:
+very_long_name.m:123:   module `parent_module:sub_module' is inaccessible.
+very_long_name.m:123:   Either there was no `import_module' or `use_module'
+very_long_name.m:123:   declaration importing module `parent_module',
+very_long_name.m:123:   or the interface for module `parent_module'
+very_long_name.m:123:   does not contain an `include_module' declaration
+very_long_name.m:123:   for module `sub_module'.
+*/
+
+report_inaccessible_module_error(ModuleName, ParentModule, SubModule,
+		Item - Context) -->
+	{ Item = module_defn(_, import(module(_))) ->
+		DeclName = "import_module"
+	; Item = module_defn(_, use(module(_))) ->
+		DeclName = "use_module"
+	;
+		error("report_inaccessible_parent_error: invalid item")
+	},
+	prog_out__write_context(Context),
+	io__write_string("In module `"),
+	prog_out__write_sym_name(ModuleName),
+	io__write_string("':\n"),
+	prog_out__write_context(Context),
+	io__write_strings(["  error in `", DeclName, "' declaration:\n"]),
+	prog_out__write_context(Context),
+	io__write_string("  module `"),
+	prog_out__write_sym_name(qualified(ParentModule, SubModule)),
+	io__write_string("' is inaccessible.\n"),
+	globals__io_lookup_bool_option(verbose_errors, VerboseErrors),
+	( { VerboseErrors = yes } ->
+		prog_out__write_context(Context),
+		io__write_string("  Either there was no `import_module' or "),
+		io__write_string("`use_module'\n"),
+		prog_out__write_context(Context),
+		io__write_string("  declaration to import module `"),
+		prog_out__write_sym_name(ParentModule),
+		io__write_string("',\n"),
+		prog_out__write_context(Context),
+		io__write_string("  or the interface for module `"),
+		prog_out__write_sym_name(ParentModule),
+		io__write_string("\n"),
+		prog_out__write_context(Context),
+		io__write_strings(["  does not contain an `include_module' ",
+					"declaration\n"]),
+		prog_out__write_context(Context),
+		io__write_string("  for module `"),
+		io__write_string(SubModule),
+		io__write_string("'.\n")
+	;
+		[]
+	),
+	io__set_exit_status(1).
 
 %-----------------------------------------------------------------------------%
 
Index: doc/reference_manual.texi
===================================================================
RCS file: /home/mercury1/repository/mercury/doc/reference_manual.texi,v
retrieving revision 1.89
diff -u -r1.89 reference_manual.texi
--- reference_manual.texi	1998/03/18 07:58:45	1.89
+++ reference_manual.texi	1998/03/18 17:18:11
@@ -2702,12 +2702,9 @@
 
 @itemize @bullet
 @item it supports only separate sub-modules, not nested sub-modules; 
- at item module qualifiers must use the fully-qualified module name; 
- at item the compiler doesn't check that the parent module is imported/used
-      before allowing import/use of its sub-modules;
- at item it doesn't check that there is an include_module declaration in the
-	parent for each module claiming to be a child of that parent;
- at item privacy of private sub-modules is not enforced
+ at item if you mix an @samp{import_module} declaration for a parent module
+      with a @samp{use_module} declaration for the child, or vice versa,
+      then the compiler may report some spurious errors.
 @end itemize
 
 @node Type classes
Index: tests/invalid/Mmakefile
===================================================================
RCS file: /home/mercury1/repository/tests/invalid/Mmakefile,v
retrieving revision 1.15
diff -u -r1.15 Mmakefile
--- Mmakefile	1998/03/18 10:22:12	1.15
+++ Mmakefile	1998/03/18 16:55:31
@@ -38,6 +38,7 @@
 	prog_io_erroneous.m \
 	qual_basic_test2.m \
 	qualified_cons_id2.m \
+	test_nested.m \
 	type_inf_loop.m \
 	type_loop.m \
 	type_mismatch.m \
@@ -53,6 +54,7 @@
 	vars_in_wrong_places.m
 
 # we do not yet pass the following tests:
+#	parent.undeclared_child.m
 #	freefree.m 	(need bromage's aliasing stuff)
 #	no_exports.m	(this is really a WISHLIST item)
 #
@@ -83,7 +85,9 @@
 
 errs:	$(ERRS)
 
-depend:
+# we only need to make the dependencies for test cases consisting of
+# multiple modules; currently the only such test case is test_nested.m
+depend:	test_nested.depend
 
 clean:
 	rm -f *.c *.o *.err *.err2 *.d *.err_res
Index: tests/invalid/parent.m
===================================================================
RCS file: parent.m
diff -N parent.m
--- /dev/null	Thu Mar 19 04:25:16 1998
+++ parent.m	Thu Mar 19 03:44:05 1998
@@ -0,0 +1,10 @@
+% This is part of the test_nested.m test case.
+
+:- module parent.
+:- interface.
+
+:- include_module public_child.
+
+:- implementation.
+
+:- include_module private_child.
Index: tests/invalid/parent.private_child.m
===================================================================
RCS file: parent.private_child.m
diff -N parent.private_child.m
--- /dev/null	Thu Mar 19 04:25:16 1998
+++ parent.private_child.m	Thu Mar 19 03:40:20 1998
@@ -0,0 +1,11 @@
+:- module parent:private_child.
+:- interface.
+:- import_module io.
+
+:- type foo ---> bar ; baz(int).
+
+:- pred hello(io__state::di, io__state::uo) is det.
+
+:- implementation.
+
+hello --> io__write_string("parent:private_child:hello\n").
Index: tests/invalid/parent.public_child.m
===================================================================
RCS file: parent.public_child.m
diff -N parent.public_child.m
--- /dev/null	Thu Mar 19 04:25:16 1998
+++ parent.public_child.m	Thu Mar 19 03:40:20 1998
@@ -0,0 +1,11 @@
+:- module parent:public_child.
+:- interface.
+:- import_module io.
+
+:- type foo ---> bar ; baz(int).
+
+:- pred hello(io__state::di, io__state::uo) is det.
+
+:- implementation.
+
+hello --> io__write_string("parent:public_child:hello\n").
Index: tests/invalid/parent.undeclared_child.err_exp
===================================================================
RCS file: parent.undeclared_child.err_exp
diff -N parent.undeclared_child.err_exp
--- /dev/null	Thu Mar 19 04:25:16 1998
+++ parent.undeclared_child.err_exp	Thu Mar 19 03:58:08 1998
@@ -0,0 +1,4 @@
+parent.undeclared_child.m:004: Error in module `parent:undeclared_child':
+parent.undeclared_child.m:004:   module `parent' has no `include_module'
+parent.undeclared_child.m:004:   declaration for module `undeclared_child'.
+For more information, try recompiling with `-E'.
Index: tests/invalid/parent.undeclared_child.m
===================================================================
RCS file: parent.undeclared_child.m
diff -N parent.undeclared_child.m
--- /dev/null	Thu Mar 19 04:25:16 1998
+++ parent.undeclared_child.m	Thu Mar 19 03:45:22 1998
@@ -0,0 +1,14 @@
+% This module is invalid, because there is no `include_module' declaration
+% for it in parent.m.
+
+:- module parent:undeclared_child.
+:- interface.
+:- import_module io.
+
+:- type foo ---> bar ; baz(int).
+
+:- pred hello(io__state::di, io__state::uo) is det.
+
+:- implementation.
+
+hello --> io__write_string("parent:undeclared_child:hello\n").
Index: tests/invalid/parent2.child.m
===================================================================
RCS file: parent2.child.m
diff -N parent2.child.m
--- /dev/null	Thu Mar 19 04:25:16 1998
+++ parent2.child.m	Thu Mar 19 03:43:10 1998
@@ -0,0 +1,10 @@
+% This is part of the test_nested.m test case.
+
+:- module parent2:child.
+:- interface.
+
+:- type foo.
+
+:- implementation.
+
+:- type foo == int.
Index: tests/invalid/parent2.m
===================================================================
RCS file: parent2.m
diff -N parent2.m
--- /dev/null	Thu Mar 19 04:25:16 1998
+++ parent2.m	Thu Mar 19 03:43:37 1998
@@ -0,0 +1,6 @@
+% This is part of the test_nested.m test case.
+
+:- module parent2.
+:- interface.
+
+:- include_module child.
Index: tests/invalid/test_nested.err_exp
===================================================================
RCS file: test_nested.err_exp
diff -N test_nested.err_exp
--- /dev/null	Thu Mar 19 04:25:16 1998
+++ test_nested.err_exp	Thu Mar 19 03:55:58 1998
@@ -0,0 +1,12 @@
+test_nested.m:009: In module `test_nested':
+test_nested.m:009:   error in `use_module' declaration:
+test_nested.m:009:   module `parent:private_child' is inaccessible.
+test_nested.m:010: In module `test_nested':
+test_nested.m:010:   error in `use_module' declaration:
+test_nested.m:010:   module `parent:undeclared_child' is inaccessible.
+test_nested.m:013: In module `test_nested':
+test_nested.m:013:   error in `use_module' declaration:
+test_nested.m:013:   module `parent2:child' is inaccessible.
+test_nested.m:015: In definition of type `test_nested:foo'/0:
+test_nested.m:015:   error: undefined type `parent:nonexistent_child:foo'/0.
+For more information, try recompiling with `-E'.
Index: tests/invalid/test_nested.m
===================================================================
RCS file: test_nested.m
diff -N test_nested.m
--- /dev/null	Thu Mar 19 04:25:16 1998
+++ test_nested.m	Thu Mar 19 03:46:04 1998
@@ -0,0 +1,23 @@
+:- module test_nested.
+:- interface.
+
+:- type foo.
+
+:- implementation.
+:- use_module parent.
+:- use_module parent:public_child.
+:- use_module parent:private_child.
+:- use_module parent:undeclared_child.
+% :- use_module parent:nonexistent_child.
+
+:- use_module parent2:child.
+
+:- type foo --->
+	foo(
+		parent:public_child:foo,
+		parent:private_child:foo,
+		parent:undeclared_child:foo,
+		parent:nonexistent_child:foo,
+		parent2:child:foo
+	).
+

-- 
Fergus Henderson <fjh at cs.mu.oz.au>  |  "I have always known that the pursuit
WWW: <http://www.cs.mu.oz.au/~fjh>  |  of excellence is a lethal habit"
PGP: finger fjh at 128.250.37.3        |     -- the last words of T. S. Garp.



More information about the developers mailing list