35
36:- module(check,
37 [ check/0, 38 list_undefined/0, 39 list_undefined/1, 40 list_autoload/0, 41 list_redefined/0, 42 list_void_declarations/0, 43 list_trivial_fails/0, 44 list_trivial_fails/1, 45 list_strings/0, 46 list_strings/1 47 ]). 48:- use_module(library(lists)). 49:- use_module(library(pairs)). 50:- use_module(library(option)). 51:- use_module(library(apply)). 52:- use_module(library(prolog_codewalk)). 53:- use_module(library(occurs)). 54
55:- set_prolog_flag(generate_debug_info, false). 56
57:- multifile
58 trivial_fail_goal/1,
59 string_predicate/1,
60 valid_string_goal/1,
61 checker/2. 62
63:- dynamic checker/2. 64
65
77
78:- predicate_options(list_undefined/1, 1,
79 [ module_class(list(oneof([user,library])))
80 ]). 81
95
96check :-
97 checker(Checker, Message),
98 print_message(informational,check(pass(Message))),
99 catch(Checker,E,print_message(error,E)),
100 fail.
101check.
102
117
118:- thread_local
119 undef/2. 120
121list_undefined :-
122 list_undefined([]).
123
124list_undefined(Options) :-
125 merge_options(Options,
126 [ module_class([user])
127 ],
128 WalkOptions),
129 prolog_walk_code([ undefined(trace),
130 on_trace(found_undef)
131 | WalkOptions
132 ]),
133 findall(PI-From, retract(undef(PI, From)), Pairs),
134 ( Pairs == []
135 -> true
136 ; print_message(warning, check(undefined_predicates)),
137 keysort(Pairs, Sorted),
138 group_pairs_by_key(Sorted, Grouped),
139 maplist(report_undefined, Grouped)
140 ).
141
142:- public found_undef/3. 143
144found_undef(To, _Caller, From) :-
145 goal_pi(To, PI),
146 ( undef(PI, From)
147 -> true
148 ; compiled(PI)
149 -> true
150 ; assertz(undef(PI,From))
151 ).
152
153compiled(system:'$call_cleanup'/0). 154compiled(system:'$catch'/0).
155compiled(system:'$cut'/0).
156compiled(system:'$reset'/0).
157compiled(system:'$call_continuation'/1).
158compiled(system:'$shift'/1).
159
160goal_pi(M:Head, M:Name/Arity) :-
161 functor(Head, Name, Arity).
162
163report_undefined(PI-FromList) :-
164 print_message(warning, check(undefined(PI, FromList))).
165
166
177
178list_autoload :-
179 setup_call_cleanup(
180 ( current_prolog_flag(access_level, OldLevel),
181 current_prolog_flag(autoload, OldAutoLoad),
182 set_prolog_flag(access_level, system),
183 set_prolog_flag(autoload, false)
184 ),
185 list_autoload_(OldLevel),
186 ( set_prolog_flag(access_level, OldLevel),
187 set_prolog_flag(autoload, OldAutoLoad)
188 )).
189
190list_autoload_(SystemMode) :-
191 ( setof(Lib-Pred,
192 autoload_predicate(Module, Lib, Pred, SystemMode),
193 Pairs),
194 print_message(informational,
195 check(autoload(Module, Pairs))),
196 fail
197 ; true
198 ).
199
200autoload_predicate(Module, Library, Name/Arity, SystemMode) :-
201 predicate_property(Module:Head, undefined),
202 check_module_enabled(Module, SystemMode),
203 ( \+ predicate_property(Module:Head, imported_from(_)),
204 functor(Head, Name, Arity),
205 '$find_library'(Module, Name, Arity, _LoadModule, Library),
206 referenced(Module:Head, Module, _)
207 -> true
208 ).
209
210check_module_enabled(_, system) :- !.
211check_module_enabled(Module, _) :-
212 \+ import_module(Module, system).
213
217
218referenced(Term, Module, Ref) :-
219 Goal = Module:_Head,
220 current_predicate(_, Goal),
221 '$get_predicate_attribute'(Goal, system, 0),
222 \+ '$get_predicate_attribute'(Goal, imported, _),
223 nth_clause(Goal, _, Ref),
224 '$xr_member'(Ref, Term).
225
231
232list_redefined :-
233 setup_call_cleanup(
234 ( current_prolog_flag(access_level, OldLevel),
235 set_prolog_flag(access_level, system)
236 ),
237 list_redefined_,
238 set_prolog_flag(access_level, OldLevel)).
239
240list_redefined_ :-
241 current_module(Module),
242 Module \== system,
243 current_predicate(_, Module:Head),
244 \+ predicate_property(Module:Head, imported_from(_)),
245 ( global_module(Super),
246 Super \== Module,
247 '$c_current_predicate'(_, Super:Head),
248 \+ redefined_ok(Head),
249 '$syspreds':'$defined_predicate'(Super:Head),
250 \+ predicate_property(Super:Head, (dynamic)),
251 \+ predicate_property(Super:Head, imported_from(Module)),
252 functor(Head, Name, Arity)
253 -> print_message(informational,
254 check(redefined(Module, Super, Name/Arity)))
255 ),
256 fail.
257list_redefined_.
258
259redefined_ok('$mode'(_,_)).
260redefined_ok('$pldoc'(_,_,_,_)).
261redefined_ok('$pred_option'(_,_,_,_)).
262
263global_module(user).
264global_module(system).
265
269
270list_void_declarations :-
271 P = _:_,
272 ( predicate_property(P, undefined),
273 ( '$get_predicate_attribute'(P, meta_predicate, Pattern),
274 print_message(warning,
275 check(void_declaration(P, meta_predicate(Pattern))))
276 ; void_attribute(Attr),
277 '$get_predicate_attribute'(P, Attr, 1),
278 print_message(warning,
279 check(void_declaration(P, Attr)))
280 ),
281 fail
282 ; true
283 ).
284
285void_attribute(public).
286void_attribute(volatile).
287
298
299:- thread_local
300 trivial_fail/2. 301
302list_trivial_fails :-
303 list_trivial_fails([]).
304
305list_trivial_fails(Options) :-
306 merge_options(Options,
307 [ module_class([user]),
308 infer_meta_predicates(false),
309 autoload(false),
310 evaluate(false),
311 trace_reference(_),
312 on_trace(check_trivial_fail)
313 ],
314 WalkOptions),
315
316 prolog_walk_code([ source(false)
317 | WalkOptions
318 ]),
319 findall(CRef, retract(trivial_fail(clause(CRef), _)), Clauses),
320 ( Clauses == []
321 -> true
322 ; print_message(warning, check(trivial_failures)),
323 prolog_walk_code([ clauses(Clauses)
324 | WalkOptions
325 ]),
326 findall(Goal-From, retract(trivial_fail(From, Goal)), Pairs),
327 keysort(Pairs, Sorted),
328 group_pairs_by_key(Sorted, Grouped),
329 maplist(report_trivial_fail, Grouped)
330 ).
331
336
337trivial_fail_goal(pce_expansion:pce_class(_, _, template, _, _, _)).
338trivial_fail_goal(pce_host:property(system_source_prefix(_))).
339
340:- public
341 check_trivial_fail/3. 342
343check_trivial_fail(MGoal0, _Caller, From) :-
344 ( MGoal0 = M:Goal,
345 atom(M),
346 callable(Goal),
347 predicate_property(MGoal0, interpreted),
348 \+ predicate_property(MGoal0, dynamic),
349 \+ predicate_property(MGoal0, multifile),
350 \+ trivial_fail_goal(MGoal0)
351 -> ( predicate_property(MGoal0, meta_predicate(Meta))
352 -> qualify_meta_goal(MGoal0, Meta, MGoal)
353 ; MGoal = MGoal0
354 ),
355 ( clause(MGoal, _)
356 -> true
357 ; assertz(trivial_fail(From, MGoal))
358 )
359 ; true
360 ).
361
362report_trivial_fail(Goal-FromList) :-
363 print_message(warning, check(trivial_failure(Goal, FromList))).
364
368
369qualify_meta_goal(M:Goal0, Meta, M:Goal) :-
370 functor(Goal0, F, N),
371 functor(Goal, F, N),
372 qualify_meta_goal(1, M, Meta, Goal0, Goal).
373
374qualify_meta_goal(N, M, Meta, Goal0, Goal) :-
375 arg(N, Meta, ArgM),
376 !,
377 arg(N, Goal0, Arg0),
378 arg(N, Goal, Arg),
379 N1 is N + 1,
380 ( module_qualified(ArgM)
381 -> add_module(Arg0, M, Arg)
382 ; Arg = Arg0
383 ),
384 meta_goal(N1, Meta, Goal0, Goal).
385meta_goal(_, _, _, _).
386
387add_module(Arg, M, M:Arg) :-
388 var(Arg),
389 !.
390add_module(M:Arg, _, MArg) :-
391 !,
392 add_module(Arg, M, MArg).
393add_module(Arg, M, M:Arg).
394
395module_qualified(N) :- integer(N), !.
396module_qualified(:).
397module_qualified(^).
398
399
414
415list_strings :-
416 list_strings([module_class([user])]).
417
418list_strings(Options) :-
419 ( prolog_program_clause(ClauseRef, Options),
420 clause(Head, Body, ClauseRef),
421 \+ ( predicate_indicator(Head, PI),
422 string_predicate(PI)
423 ),
424 make_clause(Head, Body, Clause),
425 findall(T,
426 ( sub_term(T, Head),
427 string(T)
428 ; Head = M:_,
429 goal_in_body(Goal, M, Body),
430 ( valid_string_goal(Goal)
431 -> fail
432 ; sub_term(T, Goal),
433 string(T)
434 )
435 ), Ts0),
436 sort(Ts0, Ts),
437 member(T, Ts),
438 message_context(ClauseRef, T, Clause, Context),
439 print_message(warning,
440 check(string_in_clause(T, Context))),
441 fail
442 ; true
443 ).
444
445make_clause(Head, true, Head) :- !.
446make_clause(Head, Body, (Head:-Body)).
447
451
452goal_in_body(M:G, M, G) :-
453 var(G),
454 !.
455goal_in_body(G, _, M:G0) :-
456 atom(M),
457 !,
458 goal_in_body(G, M, G0).
459goal_in_body(G, M, Control) :-
460 nonvar(Control),
461 control(Control, Subs),
462 !,
463 member(Sub, Subs),
464 goal_in_body(G, M, Sub).
465goal_in_body(G, M, G0) :-
466 callable(G0),
467 ( atom(M)
468 -> TM = M
469 ; TM = system
470 ),
471 predicate_property(TM:G0, meta_predicate(Spec)),
472 !,
473 ( strip_goals(G0, Spec, G1),
474 simple_goal_in_body(G, M, G1)
475 ; arg(I, Spec, Meta),
476 arg(I, G0, G1),
477 extend(Meta, G1, G2),
478 goal_in_body(G, M, G2)
479 ).
480goal_in_body(G, M, G0) :-
481 simple_goal_in_body(G, M, G0).
482
483simple_goal_in_body(G, M, G0) :-
484 ( atom(M),
485 callable(G0),
486 predicate_property(M:G0, imported_from(M2))
487 -> G = M2:G0
488 ; G = M:G0
489 ).
490
491control((A,B), [A,B]).
492control((A;B), [A,B]).
493control((A->B), [A,B]).
494control((A*->B), [A,B]).
495control((\+A), [A]).
496
497strip_goals(G0, Spec, G) :-
498 functor(G0, Name, Arity),
499 functor(G, Name, Arity),
500 strip_goal_args(1, G0, Spec, G).
501
502strip_goal_args(I, G0, Spec, G) :-
503 arg(I, G0, A0),
504 !,
505 arg(I, Spec, M),
506 ( extend(M, A0, _)
507 -> arg(I, G, '<meta-goal>')
508 ; arg(I, G, A0)
509 ),
510 I2 is I + 1,
511 strip_goal_args(I2, G0, Spec, G).
512strip_goal_args(_, _, _, _).
513
514extend(I, G0, G) :-
515 callable(G0),
516 integer(I), I>0,
517 !,
518 length(L, I),
519 extend_list(G0, L, G).
520extend(0, G, G).
521extend(^, G, G).
522
523extend_list(M:G0, L, M:G) :-
524 !,
525 callable(G0),
526 extend_list(G0, L, G).
527extend_list(G0, L, G) :-
528 G0 =.. List,
529 append(List, L, All),
530 G =.. All.
531
532
533message_context(ClauseRef, String, Clause, file_term_position(File, StringPos)) :-
534 clause_info(ClauseRef, File, TermPos, _Vars),
535 prolog_codewalk:subterm_pos(String, Clause, ==, TermPos, StringPos),
536 !.
537message_context(ClauseRef, _String, _Clause, file(File, Line, -1, _)) :-
538 clause_property(ClauseRef, file(File)),
539 clause_property(ClauseRef, line_count(Line)),
540 !.
541message_context(ClauseRef, _String, _Clause, clause(ClauseRef)).
542
543
544:- meta_predicate
545 predicate_indicator(:, -). 546
547predicate_indicator(Module:Head, Module:Name/Arity) :-
548 functor(Head, Name, Arity).
549predicate_indicator(Module:Head, Module:Name//DCGArity) :-
550 functor(Head, Name, Arity),
551 DCGArity is Arity-2.
552
557
558string_predicate(_:'$pldoc'/4).
559string_predicate(pce_principal:send_implementation/3).
560string_predicate(pce_principal:pce_lazy_get_method/3).
561string_predicate(pce_principal:pce_lazy_send_method/3).
562string_predicate(pce_principal:pce_class/6).
563string_predicate(prolog_xref:pred_comment/4).
564string_predicate(prolog_xref:module_comment/3).
565string_predicate(pldoc_process:structured_comment//2).
566string_predicate(pldoc_process:structured_command_start/3).
567string_predicate(pldoc_process:separator_line//0).
568string_predicate(pldoc_register:mydoc/3).
569string_predicate(http_header:separators/1).
570
576
578valid_string_goal(system:format(S)) :- string(S).
579valid_string_goal(system:format(S,_)) :- string(S).
580valid_string_goal(system:format(_,S,_)) :- string(S).
581valid_string_goal(system:string_codes(S,_)) :- string(S).
582valid_string_goal(system:string_code(_,S,_)) :- string(S).
583valid_string_goal(system:throw(msg(S,_))) :- string(S).
584valid_string_goal('$dcg':phrase(S,_,_)) :- string(S).
585valid_string_goal('$dcg':phrase(S,_)) :- string(S).
586valid_string_goal(system: is(_,_)). 587valid_string_goal(system: =:=(_,_)).
588valid_string_goal(system: >(_,_)).
589valid_string_goal(system: <(_,_)).
590valid_string_goal(system: >=(_,_)).
591valid_string_goal(system: =<(_,_)).
593valid_string_goal(dcg_basics:string_without(S,_,_,_)) :- string(S).
594valid_string_goal(git:read_url(S,_,_)) :- string(S).
595valid_string_goal(tipc:tipc_subscribe(_,_,_,_,S)) :- string(S).
596valid_string_goal(charsio:format_to_chars(Format,_,_)) :- string(Format).
597valid_string_goal(charsio:format_to_chars(Format,_,_,_)) :- string(Format).
598valid_string_goal(codesio:format_to_codes(Format,_,_)) :- string(Format).
599valid_string_goal(codesio:format_to_codes(Format,_,_,_)) :- string(Format).
600
601
602 605
625
626checker(list_undefined, 'undefined predicates').
627checker(list_trivial_fails, 'trivial failures').
628checker(list_redefined, 'redefined system and global predicates').
629checker(list_void_declarations, 'predicates with declarations but without clauses').
630checker(list_autoload, 'predicates that need autoloading').
631
632
633 636
637:- multifile
638 prolog:message/3. 639
640prolog:message(check(pass(Comment))) -->
641 [ 'Checking ~w ...'-[Comment] ].
642prolog:message(check(find_references(Preds))) -->
643 { length(Preds, N)
644 },
645 [ 'Scanning for references to ~D possibly undefined predicates'-[N] ].
646prolog:message(check(undefined_predicates)) -->
647 [ 'The predicates below are not defined. If these are defined', nl,
648 'at runtime using assert/1, use :- dynamic Name/Arity.', nl, nl
649 ].
650prolog:message(check(undefined(Pred, Refs))) -->
651 { map_list_to_pairs(sort_reference_key, Refs, Keyed),
652 keysort(Keyed, KeySorted),
653 pairs_values(KeySorted, SortedRefs)
654 },
655 predicate(Pred),
656 [ ', which is referenced by', nl ],
657 referenced_by(SortedRefs).
658prolog:message(check(undefined_unreferenced_predicates)) -->
659 [ 'The predicates below are not defined, and are not', nl,
660 'referenced.', nl, nl
661 ].
662prolog:message(check(undefined_unreferenced(Pred))) -->
663 predicate(Pred).
664prolog:message(check(autoload(Module, Pairs))) -->
665 { module_property(Module, file(Path))
666 },
667 !,
668 [ 'Into module ~w ('-[Module] ],
669 short_filename(Path),
670 [ ')', nl ],
671 autoload(Pairs).
672prolog:message(check(autoload(Module, Pairs))) -->
673 [ 'Into module ~w'-[Module], nl ],
674 autoload(Pairs).
675prolog:message(check(redefined(In, From, Pred))) -->
676 predicate(In:Pred),
677 redefined(In, From).
678prolog:message(check(trivial_failures)) -->
679 [ 'The following goals fail because there are no matching clauses.' ].
680prolog:message(check(trivial_failure(Goal, Refs))) -->
681 { map_list_to_pairs(sort_reference_key, Refs, Keyed),
682 keysort(Keyed, KeySorted),
683 pairs_values(KeySorted, SortedRefs)
684 },
685 goal(Goal),
686 [ ', which is called from'-[], nl ],
687 referenced_by(SortedRefs).
688prolog:message(check(string_in_clause(String, Context))) -->
689 prolog:message_location(Context),
690 [ 'String ~q'-[String] ].
691prolog:message(check(void_declaration(P, Decl))) -->
692 predicate(P),
693 [ ' is declared as ~p, but has no clauses'-[Decl] ].
694
695
696redefined(user, system) -->
697 [ '~t~30| System predicate redefined globally' ].
698redefined(_, system) -->
699 [ '~t~30| Redefined system predicate' ].
700redefined(_, user) -->
701 [ '~t~30| Redefined global predicate' ].
702
703goal(user:Goal) -->
704 !,
705 [ '~p'-[Goal] ].
706goal(Goal) -->
707 !,
708 [ '~p'-[Goal] ].
709
710predicate(Module:Name/Arity) -->
711 { atom(Module),
712 atom(Name),
713 integer(Arity),
714 functor(Head, Name, Arity),
715 predicate_name(Module:Head, PName)
716 },
717 !,
718 [ '~w'-[PName] ].
719predicate(Module:Head) -->
720 { atom(Module),
721 callable(Head),
722 predicate_name(Module:Head, PName)
723 },
724 !,
725 [ '~w'-[PName] ].
726predicate(Name/Arity) -->
727 { atom(Name),
728 integer(Arity)
729 },
730 !,
731 predicate(user:Name/Arity).
732
733autoload([]) -->
734 [].
735autoload([Lib-Pred|T]) -->
736 [ ' ' ],
737 predicate(Pred),
738 [ '~t~24| from ' ],
739 short_filename(Lib),
740 [ nl ],
741 autoload(T).
742
746
747sort_reference_key(Term, key(M:Name/Arity, N, ClausePos)) :-
748 clause_ref(Term, ClauseRef, ClausePos),
749 !,
750 nth_clause(Pred, N, ClauseRef),
751 strip_module(Pred, M, Head),
752 functor(Head, Name, Arity).
753sort_reference_key(Term, Term).
754
755clause_ref(clause_term_position(ClauseRef, TermPos), ClauseRef, ClausePos) :-
756 arg(1, TermPos, ClausePos).
757clause_ref(clause(ClauseRef), ClauseRef, 0).
758
759
760referenced_by([]) -->
761 [].
762referenced_by([Ref|T]) -->
763 ['\t'], prolog:message_location(Ref),
764 predicate_indicator(Ref),
765 [ nl ],
766 referenced_by(T).
767
768predicate_indicator(clause_term_position(ClauseRef, _)) -->
769 { nonvar(ClauseRef) },
770 !,
771 predicate_indicator(clause(ClauseRef)).
772predicate_indicator(clause(ClauseRef)) -->
773 { clause_name(ClauseRef, Name) },
774 [ '~w'-[Name] ].
775predicate_indicator(file_term_position(_,_)) -->
776 [ '(initialization)' ].
777predicate_indicator(file(_,_,_,_)) -->
778 [ '(initialization)' ].
779
780
781short_filename(Path) -->
782 { short_filename(Path, Spec)
783 },
784 [ '~q'-[Spec] ].
785
786short_filename(Path, Spec) :-
787 absolute_file_name('', Here),
788 atom_concat(Here, Local0, Path),
789 !,
790 remove_leading_slash(Local0, Spec).
791short_filename(Path, Spec) :-
792 findall(LenAlias, aliased_path(Path, LenAlias), Keyed),
793 keysort(Keyed, [_-Spec|_]).
794short_filename(Path, Path).
795
796aliased_path(Path, Len-Spec) :-
797 setof(Alias, Spec^(user:file_search_path(Alias, Spec)), Aliases),
798 member(Alias, Aliases),
799 Term =.. [Alias, '.'],
800 absolute_file_name(Term,
801 [ file_type(directory),
802 file_errors(fail),
803 solutions(all)
804 ], Prefix),
805 atom_concat(Prefix, Local0, Path),
806 remove_leading_slash(Local0, Local),
807 atom_length(Local, Len),
808 Spec =.. [Alias, Local].
809
810remove_leading_slash(Path, Local) :-
811 atom_concat(/, Local, Path),
812 !.
813remove_leading_slash(Path, Path)