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