1 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
2 // -*- Mode: C++ -*-
3 //
4 // Copyright (C) 2013-2021 Red Hat, Inc.
5
6 ///@file
7
8 // In case we have a bad fts we include this before config.h because
9 // it can't handle _FILE_OFFSET_BITS. Everything we need here is fine
10 // if its declarations just come first. Also, include sys/types.h
11 // before fts. On some systems fts.h is not self contained.
12 #ifdef BAD_FTS
13 #include <sys/types.h>
14 #include <fts.h>
15 #endif
16
17 // For package configuration macros.
18 #include "config.h"
19
20 // In case we don't have a bad fts then we need to include fts.h after
21 // config.h.
22 #ifndef BAD_FTS
23 #include <sys/types.h>
24 #include <fts.h>
25 #endif
26
27 #include <unistd.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <sys/time.h>
31 #include <dirent.h>
32 #include <time.h>
33 #include <ctype.h>
34 #include <errno.h>
35 #include <libgen.h>
36
37 #include <algorithm>
38 #include <cstdlib>
39 #include <cstring>
40 #include <fstream>
41 #include <iostream>
42 #include <iterator>
43 #include <memory>
44 #include <sstream>
45
46 #include "abg-dwarf-reader.h"
47 #ifdef WITH_CTF
48 #include "abg-ctf-reader.h"
49 #endif
50 #include "abg-internal.h"
51 #include "abg-regex.h"
52
53 // <headers defining libabigail's API go under here>
54 ABG_BEGIN_EXPORT_DECLARATIONS
55
56 #include <abg-ir.h>
57 #include "abg-config.h"
58 #include "abg-tools-utils.h"
59
60 ABG_END_EXPORT_DECLARATIONS
61 // </headers defining libabigail's API>
62
63 using std::string;
64
65 namespace abigail
66 {
67
68 using namespace abigail::suppr;
69 using namespace abigail::ini;
70
71 /// @brief Namespace for a set of utility function used by tools based
72 /// on libabigail.
73 namespace tools_utils
74 {
75
76 /// Get the value of $libdir variable of the autotools build
77 /// system. This is where shared libraries are usually installed.
78 ///
79 /// @return a constant string (doesn't have to be free-ed by the
80 /// caller) that represent the value of the $libdir variable in the
81 /// autotools build system, or NULL if it's not set.
82 const char*
get_system_libdir()83 get_system_libdir()
84 {
85 #ifndef ABIGAIL_ROOT_SYSTEM_LIBDIR
86 #error the macro ABIGAIL_ROOT_SYSTEM_LIBDIR must be set at compile time
87 #endif
88
89 static __thread const char* system_libdir(ABIGAIL_ROOT_SYSTEM_LIBDIR);
90 return system_libdir;
91 }
92
93 /// The bitwise 'OR' operator for abidiff_status bit masks.
94 ///
95 /// @param l the left hand side operand of the OR operator.
96 ///
97 /// @param r the right hand side operand of the OR operator.
98 ///
99 /// @return the result of the OR expression.
100 abidiff_status
operator |(abidiff_status l,abidiff_status r)101 operator|(abidiff_status l, abidiff_status r)
102 {return static_cast<abidiff_status>(static_cast<unsigned>(l)
103 | static_cast<unsigned>(r));}
104
105 /// The bitwise 'AND' operator for abidiff_status bit masks.
106 ///
107 /// @param l the left hand side operand of the AND operator.
108 ///
109 /// @param r the right hand side operand of the AND operator.
110 ///
111 /// @return the result of the AND expression.
112 abidiff_status
operator &(abidiff_status l,abidiff_status r)113 operator&(abidiff_status l, abidiff_status r)
114 {return static_cast<abidiff_status>(static_cast<unsigned>(l)
115 & static_cast<unsigned>(r));}
116
117 /// The |= operator.
118 ///
119 /// @param l the left hand side operand of the operator.
120 ///
121 /// @param r the right hand side operand of the operator.
122 ///
123 /// @param the resulting bit mask.
124 abidiff_status&
operator |=(abidiff_status & l,abidiff_status r)125 operator|=(abidiff_status&l, abidiff_status r)
126 {
127 l = static_cast<abidiff_status>(static_cast<unsigned>(l)
128 | static_cast<unsigned>(r));
129 return l;
130 }
131
132 /// Test if an instance of @param abidiff_status bits mask represents
133 /// an error.
134 ///
135 /// This functions tests if the @ref ABIDIFF_ERROR bit is set in the
136 /// given bits mask.
137 ///
138 /// @param s the bit mask to consider.
139 ///
140 /// @return true iff @p s has its ABIDIFF_ERROR bit set.
141 bool
abidiff_status_has_error(abidiff_status s)142 abidiff_status_has_error(abidiff_status s)
143 {return s & (ABIDIFF_ERROR | ABIDIFF_USAGE_ERROR);}
144
145 /// Test if an instance of @param abidiff_status bits mask represents
146 /// an abi change.
147 ///
148 /// This functions tests if the @ref ABIDIFF_ABI_CHANGE bit is set in the
149 /// given bits mask.
150 ///
151 /// @param s the bit mask to consider.
152 ///
153 /// @return true iff @p s has its @ref ABIDIFF_ABI_CHANGE bit set.
154 bool
abidiff_status_has_abi_change(abidiff_status s)155 abidiff_status_has_abi_change(abidiff_status s)
156 {return s & ABIDIFF_ABI_CHANGE;}
157
158 /// Test if an instance of @param abidiff_status bits mask represents
159 /// an incompatible abi change.
160 ///
161 /// This functions tests if the @ref ABIDIFF_INCOMPATIBLE_ABI_CHANGE
162 /// bit is set in the given bits mask. Note that the this bit is set
163 /// then the bit @ref ABIDIFF_ABI_CHANGE must be set as well.
164 ///
165 /// @param s the bit mask to consider.
166 ///
167 /// @return true iff @p s has its @ref ABIDIFF_INCOMPATIBLE ABI_CHANGE
168 /// set.
169 bool
abidiff_status_has_incompatible_abi_change(abidiff_status s)170 abidiff_status_has_incompatible_abi_change(abidiff_status s)
171 {return s & ABIDIFF_ABI_INCOMPATIBLE_CHANGE;}
172
173 #define DECLARE_STAT(st) \
174 struct stat st; \
175 memset(&st, 0, sizeof(st))
176
177 // <class timer stuff>
178
179 /// The private data type of the @ref timer class.
180 struct timer::priv
181 {
182 timer::kind timer_kind;
183 struct timeval begin_timeval;
184 struct timeval end_timeval;
185
privabigail::tools_utils::timer::priv186 priv(timer::kind k)
187 : timer_kind(k),
188 begin_timeval(),
189 end_timeval()
190 {}
191 }; // end struct timer::priv
192
193 /// Constructor of the @ref timer type.
194 ///
195 /// @param k the kind of timer to instantiate.
timer(timer::kind k)196 timer::timer(timer::kind k)
197 : priv_(new timer::priv(k))
198 {
199 if (priv_->timer_kind == START_ON_INSTANTIATION_TIMER_KIND)
200 start();
201 }
202
203 /// Start the timer.
204 ///
205 /// To stop the timer (and record the time elapsed since the timer was
206 /// started), call the timer::stop member function.
207 ///
208 /// @return true upon successful completion.
209 bool
start()210 timer::start()
211 {
212 if (gettimeofday(&priv_->begin_timeval, 0))
213 return false;
214 return true;
215 }
216
217 /// Stop the timer.
218 ///
219 /// This records the time elapsed since the timer was started using
220 /// the timer::start member function.
221 ///
222 /// @return true upon successful completion.
223 bool
stop()224 timer::stop()
225 {
226 if (gettimeofday(&priv_->end_timeval, 0))
227 return false;
228 return true;
229 }
230
231 /// Get the elapsed time in seconds.
232 ///
233 /// @return the time elapsed between the invocation of the methods
234 /// timer::start() and timer::stop, in seconds.
235 time_t
value_in_seconds() const236 timer::value_in_seconds() const
237 {return priv_->end_timeval.tv_sec - priv_->begin_timeval.tv_sec;}
238
239 /// Get the elapsed time in hour:minutes:seconds:milliseconds.
240 ///
241 /// @param hours out parameter. This is set to the number of hours elapsed.
242 ///
243 /// @param minutes out parameter. This is set to the number of minutes
244 /// (passed the number of hours) elapsed.
245 ///
246 /// @param seconds out parameter. This is se to the number of
247 /// seconds (passed the number of hours and minutes) elapsed.
248 ///
249 /// @param milliseconds. This is set ot the number of milliseconds
250 /// (passed the number of hours, minutes and seconds) elapsed.
251 ///
252 /// @return true upon successful completion.
253 bool
value(time_t & hours,time_t & minutes,time_t & seconds,time_t & milliseconds) const254 timer::value(time_t& hours,
255 time_t& minutes,
256 time_t& seconds,
257 time_t& milliseconds) const
258 {
259 time_t elapsed_seconds =
260 priv_->end_timeval.tv_sec - priv_->begin_timeval.tv_sec;
261 suseconds_t elapsed_usecs =
262 ((priv_->end_timeval.tv_sec * 1000000) + priv_->end_timeval.tv_usec)
263 - ((priv_->begin_timeval.tv_sec * 1000000) + priv_->begin_timeval.tv_usec);
264
265 milliseconds = 0;
266
267 hours = elapsed_seconds / 3600;
268 minutes = (elapsed_seconds % 3600) / 60;
269 seconds = (elapsed_seconds % 3600) % 60;
270 if (elapsed_seconds == 0)
271 milliseconds = elapsed_usecs / 1000;
272
273 return true;
274 }
275
276 /// Get the elapsed time as a human-readable string.
277 ///
278 /// @return the elapsed time as a human-readable string.
279 string
value_as_string() const280 timer::value_as_string() const
281 {
282 time_t hours = 0, minutes = 0, seconds = 0;
283 time_t msecs = 0;
284
285 value(hours, minutes, seconds, msecs);
286
287 std::ostringstream o;
288
289 if (hours)
290 o << hours << "h";
291
292 if (minutes)
293 o << minutes << "m";
294
295 o << seconds << "s";
296
297 if (msecs)
298 o <<msecs <<"ms";
299
300 return o.str();
301 }
302
303 /// Destructor of the @ref timer type.
~timer()304 timer::~timer()
305 {
306 }
307
308 /// Streaming operator for the @ref timer type.
309 ///
310 /// Emit a string representing the elapsed time (in a human-readable
311 /// manner) to an output stream.
312 ///
313 /// @param o the output stream to emit the elapsed time string to.
314 ///
315 /// @param t the timer to consider.
316 ///
317 /// @return the output stream considered.
318 ostream&
operator <<(ostream & o,const timer & t)319 operator<<(ostream& o, const timer& t)
320 {
321 o << t.value_as_string();
322 return o;
323 }
324
325 /// Get the stat struct (as returned by the lstat() function of the C
326 /// library) of a file. Note that the function uses lstat, so that
327 /// callers can detect symbolic links.
328 ///
329 /// @param path the path to the function to stat.
330 ///
331 /// @param s the resulting stat struct.
332 ///
333 /// @return true iff the stat function completed successfully.
334 static bool
get_stat(const string & path,struct stat * s)335 get_stat(const string& path,
336 struct stat* s)
337 {return (lstat(path.c_str(), s) == 0);}
338
339 /// Tests whether a path exists;
340 ///
341 /// @param path the path to test for.
342 ///
343 /// @return true iff the path at @p path exist.
344 bool
file_exists(const string & path)345 file_exists(const string& path)
346 {
347 DECLARE_STAT(st);
348
349 return get_stat(path, &st);
350 }
351
352 /// Test that a given directory exists.
353 ///
354 /// @param path the path of the directory to consider.
355 ///
356 /// @return true iff a directory exists with the name @p path
357 bool
dir_exists(const string & path)358 dir_exists(const string &path)
359 {return file_exists(path) && is_dir(path);}
360
361 /// Test if a given directory exists and is empty.
362 ///
363 /// @param path the path of the directory to consider
364 bool
dir_is_empty(const string & path)365 dir_is_empty(const string &path)
366 {
367 if (!dir_exists(path))
368 return false;
369
370 DIR* dir = opendir(path.c_str());
371 if (!dir)
372 return false;
373
374 errno = 0;
375 dirent *result = readdir(dir);
376 if (result == NULL && errno != 0)
377 return false;
378
379 closedir(dir);
380
381 return result == NULL;
382 }
383
384 /// Test if path is a path to a regular file or a symbolic link to a
385 /// regular file.
386 ///
387 /// @param path the path to consider.
388 ///
389 /// @return true iff path is a regular path.
390 bool
is_regular_file(const string & path)391 is_regular_file(const string& path)
392 {
393 DECLARE_STAT(st);
394
395 if (!get_stat(path, &st))
396 return false;
397
398 if (S_ISREG(st.st_mode))
399 return true;
400
401 string symlink_target_path;
402 if (maybe_get_symlink_target_file_path(path, symlink_target_path))
403 return is_regular_file(symlink_target_path);
404
405 return false;
406 }
407
408 /// Tests if a given path is a directory or a symbolic link to a
409 /// directory.
410 ///
411 /// @param path the path to test for.
412 ///
413 /// @return true iff @p path is a directory.
414 bool
is_dir(const string & path)415 is_dir(const string& path)
416 {
417 DECLARE_STAT(st);
418
419 if (!get_stat(path, &st))
420 return false;
421
422 if (S_ISDIR(st.st_mode))
423 return true;
424
425 string symlink_target_path;
426 if (maybe_get_symlink_target_file_path(path, symlink_target_path))
427 return is_dir(symlink_target_path);
428
429 return false;
430 }
431
432 static const char* ANONYMOUS_STRUCT_INTERNAL_NAME = "__anonymous_struct__";
433 static const char* ANONYMOUS_UNION_INTERNAL_NAME = "__anonymous_union__";
434 static const char* ANONYMOUS_ENUM_INTERNAL_NAME = "__anonymous_enum__";
435
436 static int ANONYMOUS_STRUCT_INTERNAL_NAME_LEN =
437 strlen(ANONYMOUS_STRUCT_INTERNAL_NAME);
438
439 static int ANONYMOUS_UNION_INTERNAL_NAME_LEN =
440 strlen(ANONYMOUS_UNION_INTERNAL_NAME);
441
442 static int ANONYMOUS_ENUM_INTERNAL_NAME_LEN =
443 strlen(ANONYMOUS_ENUM_INTERNAL_NAME);
444
445 /// Getter of the prefix for the name of anonymous structs.
446 ///
447 /// @reaturn the prefix for the name of anonymous structs.
448 const char*
get_anonymous_struct_internal_name_prefix()449 get_anonymous_struct_internal_name_prefix()
450 {return ANONYMOUS_STRUCT_INTERNAL_NAME;}
451
452 /// Getter of the prefix for the name of anonymous unions.
453 ///
454 /// @reaturn the prefix for the name of anonymous unions.
455 const char*
get_anonymous_union_internal_name_prefix()456 get_anonymous_union_internal_name_prefix()
457 {return ANONYMOUS_UNION_INTERNAL_NAME;}
458
459 /// Getter of the prefix for the name of anonymous enums.
460 ///
461 /// @reaturn the prefix for the name of anonymous enums.
462 const char*
get_anonymous_enum_internal_name_prefix()463 get_anonymous_enum_internal_name_prefix()
464 {return ANONYMOUS_ENUM_INTERNAL_NAME;}
465
466 /// Compare two fully qualified decl names by taking into account that
467 /// they might have compontents that are anonymous types/namespace names.
468 ///
469 /// For instance:
470 ///
471 /// __anonymous_struct__1::foo and __anonymous_struct__2::foo are
472 /// considered being equivalent qualified names because both are data
473 /// members that belong to anonymous structs. The anonymous structs
474 /// are numbered so that we can tell them appart (and look them up)
475 /// where there are several of them in the same scope. But during
476 /// comparison, for various purposes, we want to consider them as
477 /// equivalent.
478 ///
479 /// Similarly, __anonymous_struct__1::foo::__anonymous_struct__2::bar
480 /// and __anonymous_struct__10::foo::__anonymous_struct__11::bar are
481 /// equivalent.
482 ///
483 /// But __anonymous_struct__1::foo::__anonymous_struct__2::bar and
484 /// __anonymous_struct__10::foo::__anonymous_union__11::bar are not
485 /// equivalent because the former designates a member of an anonymous
486 /// struct and the latter designates a member of an anonymous union.
487 ///
488 /// So this function handles those cases.
489 ///
490 /// @param l the name of the first (left hand side) decl to consider.
491 ///
492 /// @param r the name of the second (right hand side) decl to consider.
493 ///
494 /// @return true iff @p l is equivalent to @p r when taking into
495 /// account the anonymous scopes that both might have and if they
496 /// might be anonymous themselves.
497 bool
decl_names_equal(const string & l,const string & r)498 decl_names_equal(const string& l, const string& r)
499 {
500 string::size_type l_pos1 = 0, r_pos1 = 0;
501 const string::size_type l_length = l.length(), r_length = r.length();
502
503 while (l_pos1 < l_length && r_pos1 < r_length)
504 {
505 string::size_type l_pos2 = l.find("::", l_pos1);
506 string::size_type r_pos2 = r.find("::", r_pos1);
507 if (l_pos2 == string::npos)
508 l_pos2 = l_length;
509 if (r_pos2 == string::npos)
510 r_pos2 = r_length;
511
512 if (l.compare(l_pos1, l_pos2 - l_pos1, r,
513 r_pos1, r_pos2 - r_pos1)
514 && (l.compare(l_pos1,
515 ANONYMOUS_STRUCT_INTERNAL_NAME_LEN,
516 ANONYMOUS_STRUCT_INTERNAL_NAME)
517 || r.compare(r_pos1,
518 ANONYMOUS_STRUCT_INTERNAL_NAME_LEN,
519 ANONYMOUS_STRUCT_INTERNAL_NAME))
520 && (l.compare(l_pos1,
521 ANONYMOUS_UNION_INTERNAL_NAME_LEN,
522 ANONYMOUS_UNION_INTERNAL_NAME)
523 || r.compare(r_pos1,
524 ANONYMOUS_UNION_INTERNAL_NAME_LEN,
525 ANONYMOUS_UNION_INTERNAL_NAME))
526 && (l.compare(l_pos1,
527 ANONYMOUS_ENUM_INTERNAL_NAME_LEN,
528 ANONYMOUS_ENUM_INTERNAL_NAME)
529 || r.compare(r_pos1,
530 ANONYMOUS_ENUM_INTERNAL_NAME_LEN,
531 ANONYMOUS_ENUM_INTERNAL_NAME)))
532 return false;
533
534 l_pos1 = l_pos2 == l_length ? l_pos2 : l_pos2 + 2;
535 r_pos1 = r_pos2 == r_length ? r_pos2 : r_pos2 + 2;
536 }
537
538 return (l_pos1 == l_length) == (r_pos1 == r_length);
539 }
540
541 /// If a given file is a symbolic link, get the canonicalized absolute
542 /// path to the target file.
543 ///
544 /// @param file_path the path to the file to consider.
545 ///
546 /// @param target_path this parameter is set by the function to the
547 /// canonicalized path to the target file, if @p file_path is a
548 /// symbolic link. In that case, the function returns true.
549 ///
550 /// @return true iff @p file_path is a symbolic link. In that case,
551 /// the function sets @p target_path to the canonicalized absolute
552 /// path of the target file.
553 bool
maybe_get_symlink_target_file_path(const string & file_path,string & target_path)554 maybe_get_symlink_target_file_path(const string& file_path,
555 string& target_path)
556 {
557 DECLARE_STAT(st);
558
559 if (!get_stat(file_path, &st))
560 return false;
561
562 if (!S_ISLNK(st.st_mode))
563 return false;
564
565 char *link_target_path = realpath(file_path.c_str(), NULL);
566 if (!link_target_path)
567 return false;
568
569 target_path = link_target_path;
570 free(link_target_path);
571 return true;
572 }
573
574 /// Return the directory part of a file path.
575 ///
576 /// @param path the file path to consider
577 ///
578 /// @param dirnam the resulting directory part, or "." if the couldn't
579 /// figure out anything better (for now; maybe we should do something
580 /// better than this later ...).
581 ///
582 /// @param keep_separator_at_end if true, then keep the separator at
583 /// the end of the resulting dir name.
584 ///
585 /// @return true upon successful completion, false otherwise (okay,
586 /// for now it always return true, but that might change in the future).
587 bool
dir_name(string const & path,string & dir_name,bool keep_separator_at_end)588 dir_name(string const& path,
589 string& dir_name,
590 bool keep_separator_at_end)
591 {
592 if (path.empty())
593 {
594 dir_name = ".";
595 return true;
596 }
597
598 char *p = strdup(path.c_str());
599 char *r = ::dirname(p);
600 dir_name = r;
601 free(p);
602 if (keep_separator_at_end
603 && dir_name.length() < path.length())
604 dir_name += "/";
605 return true;
606 }
607
608 /// Return the file name part of a file part.
609 ///
610 /// @param path the file path to consider.
611 ///
612 /// @param file_name the name part of the file to consider.
613 ///
614 ///@return true upon successful completion, false otherwise (okay it
615 ///always return true for now, but that might change in the future).
616 bool
base_name(string const & path,string & file_name)617 base_name(string const &path,
618 string& file_name)
619 {
620 if (path.empty())
621 {
622 file_name = ".";
623 return true;
624 }
625
626 char *p = strdup(path.c_str());
627 char *f = ::basename(p);
628 file_name = f;
629 free(p);
630 return true;
631 }
632
633 /// Return the real path of a given path.
634 ///
635 /// The real path of path 'foo_path' is the same path as foo_path, but
636 /// with symlinks and relative paths resolved.
637 ///
638 /// @param path the path to consider.
639 ///
640 /// @param result the computed real_path;
641 void
real_path(const string & path,string & result)642 real_path(const string&path, string& result)
643 {
644 if (path.empty())
645 {
646 result.clear();
647 return;
648 }
649
650 char *realp = realpath(path.c_str(), NULL);
651 if (realp)
652 {
653 result = realp;
654 free(realp);
655 }
656 }
657
658 /// Ensures #dir_path is a directory and is created. If #dir_path is
659 /// not created, this function creates it.
660 ///
661 /// \return true if #dir_path is a directory that is already present,
662 /// of if the function has successfuly created it.
663 bool
ensure_dir_path_created(const string & dir_path)664 ensure_dir_path_created(const string& dir_path)
665 {
666 struct stat st;
667 memset(&st, 0, sizeof (st));
668
669 int stat_result = 0;
670
671 stat_result = stat(dir_path.c_str(), &st);
672 if (stat_result == 0)
673 {
674 // A file or directory already exists with the same name.
675 if (!S_ISDIR (st.st_mode))
676 return false;
677 return true;
678 }
679
680 string cmd;
681 cmd = "mkdir -p " + dir_path;
682
683 if (system(cmd.c_str()))
684 return false;
685
686 return true;
687 }
688
689 /// Ensures that the parent directory of #path is created.
690 ///
691 /// \return true if the parent directory of #path is already present,
692 /// or if this function has successfuly created it.
693 bool
ensure_parent_dir_created(const string & path)694 ensure_parent_dir_created(const string& path)
695 {
696 bool is_ok = false;
697
698 if (path.empty())
699 return is_ok;
700
701 string parent;
702 if (dir_name(path, parent))
703 is_ok = ensure_dir_path_created(parent);
704
705 return is_ok;
706 }
707
708 /// Emit a prefix made of the name of the program which is emitting a
709 /// message to an output stream.
710 ///
711 /// The prefix is a string which looks like:
712 ///
713 /// "<program-name> : "
714 ///
715 /// @param prog_name the name of the program to use in the prefix.
716 /// @param out the output stream where to emit the prefix.
717 ///
718 /// @return the output stream where the prefix was emitted.
719 ostream&
emit_prefix(const string & prog_name,ostream & out)720 emit_prefix(const string& prog_name, ostream& out)
721 {
722 if (!prog_name.empty())
723 out << prog_name << ": ";
724 return out;
725 }
726
727 /// Check if a given path exists and is readable.
728 ///
729 /// @param path the path to consider.
730 ///
731 /// @param out the out stream to report errors to.
732 ///
733 /// @return true iff path exists and is readable.
734 bool
check_file(const string & path,ostream & out,const string & prog_name)735 check_file(const string& path, ostream& out, const string& prog_name)
736 {
737 if (!file_exists(path))
738 {
739 emit_prefix(prog_name, out) << "file " << path << " does not exist\n";
740 return false;
741 }
742
743 if (!is_regular_file(path))
744 {
745 emit_prefix(prog_name, out) << path << " is not a regular file\n";
746 return false;
747 }
748
749 return true;
750 }
751
752 /// Check if a given path exists, is readable and is a directory.
753 ///
754 /// @param path the path to consider.
755 ///
756 /// @param out the out stream to report errors to.
757 ///
758 /// @param prog_name the program name on behalf of which to report the
759 /// error, if any.
760 ///
761 /// @return true iff @p path exists and is for a directory.
762 bool
check_dir(const string & path,ostream & out,const string & prog_name)763 check_dir(const string& path, ostream& out, const string& prog_name)
764 {
765 if (!file_exists(path))
766 {
767 emit_prefix(prog_name, out) << "path " << path << " does not exist\n";
768 return false;
769 }
770
771 if (!is_dir(path))
772 {
773 emit_prefix(prog_name, out) << path << " is not a directory\n";
774 return false;
775 }
776
777 return true;
778 }
779
780 /// Test if a given string ends with a particular suffix.
781 ///
782 /// @param str the string to consider.
783 ///
784 /// @param suffix the suffix to test for.
785 ///
786 /// @return true iff string @p str ends with suffix @p suffix.
787 bool
string_ends_with(const string & str,const string & suffix)788 string_ends_with(const string& str, const string& suffix)
789 {
790 string::size_type str_len = str.length(), suffix_len = suffix.length();
791
792 if (str_len < suffix_len)
793 return false;
794 return str.compare(str_len - suffix_len, suffix_len, suffix) == 0;
795 }
796
797 /// Test if a given string begins with a particular prefix.
798 ///
799 /// @param str the string consider.
800 ///
801 /// @param prefix the prefix to look for.
802 ///
803 /// @return true iff string @p str begins with prefix @p prefix.
804 bool
string_begins_with(const string & str,const string & prefix)805 string_begins_with(const string& str, const string& prefix)
806 {
807 if (str.empty())
808 return false;
809
810 if (prefix.empty())
811 return true;
812
813 string::size_type prefix_len = prefix.length();
814 if (prefix_len > str.length())
815 return false;
816
817 return str.compare(0, prefix.length(), prefix) == 0;
818 }
819
820 /// Test if a string is made of ascii characters.
821 ///
822 /// @param str the string to consider.
823 ///
824 /// @return true iff @p str is made of ascii characters.
825 bool
string_is_ascii(const string & str)826 string_is_ascii(const string& str)
827 {
828 for (string::const_iterator i = str.begin(); i != str.end(); ++i)
829 if (!isascii(*i))
830 return false;
831
832 return true;
833 }
834
835 /// Test if a string is made of ascii characters which are identifiers
836 /// acceptable in C or C++ programs.
837 ///
838 ///
839 /// In the C++ spec, [lex.charset]/2, we can read:
840 ///
841 /// "if the hexadecimal value for a universal-character-name [...] or
842 /// string literal corresponds to a control character (in either of
843 /// the ranges 0x00-0x1F or 0x7F-0x9F, both inclusive) [...] the
844 /// program is ill-formed."
845 ///
846 /// @param str the string to consider.
847 ///
848 /// @return true iff @p str is made of ascii characters, and is an
849 /// identifier.
850 bool
string_is_ascii_identifier(const string & str)851 string_is_ascii_identifier(const string& str)
852 {
853 for (string::const_iterator i = str.begin(); i != str.end(); ++i)
854 {
855 unsigned char c = *i;
856 if (!isascii(c)
857 || (c <= 0x1F) // Rule out control characters
858 || (c >= 0x7F && c <= 0x9F)) // Rule out special extended
859 // ascii characters.
860 return false;
861 }
862
863 return true;
864 }
865
866 /// Split a given string into substrings, given some delimiters.
867 ///
868 /// @param input_string the input string to split.
869 ///
870 /// @param delims a string containing the delimiters to consider.
871 ///
872 /// @param result a vector of strings containing the splitted result.
873 ///
874 /// @return true iff the function found delimiters in the input string
875 /// and did split it as a result. Note that if no delimiter was found
876 /// in the input string, then the input string is added at the end of
877 /// the output vector of strings.
878 bool
split_string(const string & input_string,const string & delims,vector<string> & result)879 split_string(const string& input_string,
880 const string& delims,
881 vector<string>& result)
882 {
883 size_t current = 0, next;
884 bool did_split = false;
885
886 do
887 {
888 // Trim leading white spaces
889 while (current < input_string.size() && isspace(input_string[current]))
890 ++current;
891
892 if (current >= input_string.size())
893 break;
894
895 next = input_string.find_first_of(delims, current);
896 if (next == string::npos)
897 {
898 string s = input_string.substr(current);
899 if (!s.empty())
900 result.push_back(input_string.substr(current));
901 did_split = (current != 0);
902 break;
903 }
904 string s = input_string.substr(current, next - current);
905 if (!s.empty())
906 {
907 result.push_back(input_string.substr(current, next - current));
908 did_split = true;
909 }
910 current = next + 1;
911 }
912 while (next != string::npos);
913
914 return did_split;
915 }
916
917 /// Get the suffix of a string, given a prefix to consider.
918 ///
919 /// @param input_string the input string to consider.
920 ///
921 /// @param prefix the prefix of the input string to consider.
922 ///
923 /// @param suffix output parameter. This is set by the function to the
924 /// the computed suffix iff a suffix was found for prefix @p prefix.
925 ///
926 /// @return true iff the function could find a prefix for the suffix
927 /// @p suffix in the input string @p input_string.
928 bool
string_suffix(const string & input_string,const string & prefix,string & suffix)929 string_suffix(const string& input_string,
930 const string& prefix,
931 string& suffix)
932 {
933 // Some basic sanity check before we start hostilities.
934 if (prefix.length() >= input_string.length())
935 return false;
936
937 if (input_string.compare(0, prefix.length(), prefix) != 0)
938 // The input string does not start with the string contained in
939 // the prefix parameter.
940 return false;
941
942 suffix = input_string.substr(prefix.length());
943 return true;
944 }
945
946 /// Return the prefix that is common to two strings.
947 ///
948 /// @param s1 the first input string to consider.
949 ///
950 /// @param s2 the second input string to consider.
951 ///
952 /// @param result output parameter. The resulting common prefix found
953 /// between @p s1 and @p s2. This is set iff the function returns
954 /// true.
955 ///
956 /// @return true iff @p result was set by this function with the
957 /// common prefix of @p s1 and @p s2.
958 static bool
common_prefix(const string & s1,const string & s2,string & result)959 common_prefix(const string& s1, const string& s2, string &result)
960 {
961 if (s1.length() == 0 || s2.length() == 0)
962 return false;
963
964 result.clear();
965 for (size_t i = 0; i < s1.length() && i< s2.length(); ++i)
966 if (s1[i] == s2[i])
967 result += s1[i];
968 else
969 break;
970
971 return !result.empty();
972 }
973
974 /// Find the prefix common to a *SORTED* vector of strings.
975 ///
976 /// @param input_strings a lexycographically sorted vector of
977 /// strings. Please note that this vector absolutely needs to be
978 /// sorted for the function to work correctly. Otherwise the results
979 /// are going to be wrong.
980 ///
981 /// @param prefix output parameter. This is set by this function with
982 /// the prefix common to the strings found in @p input_strings, iff
983 /// the function returns true.
984 ///
985 /// @return true iff the function could find a common prefix to the
986 /// strings in @p input_strings.
987 bool
sorted_strings_common_prefix(vector<string> & input_strings,string & prefix)988 sorted_strings_common_prefix(vector<string>& input_strings, string& prefix)
989 {
990 string prefix_candidate;
991 bool found_prefix = false;
992
993 if (input_strings.size() == 1)
994 {
995 if (dir_name(input_strings.front(), prefix,
996 /*keep_separator_at_end=*/true))
997 return true;
998 return false;
999 }
1000
1001 string cur_str;
1002 for (vector<string>::const_iterator i = input_strings.begin();
1003 i != input_strings.end();
1004 ++i)
1005 {
1006 dir_name(*i, cur_str, /*keep_separator_at_end=*/true);
1007 if (prefix_candidate.empty())
1008 {
1009 prefix_candidate = cur_str;
1010 continue;
1011 }
1012
1013 string s;
1014 if (common_prefix(prefix_candidate, cur_str, s))
1015 {
1016 ABG_ASSERT(!s.empty());
1017 prefix_candidate = s;
1018 found_prefix = true;
1019 }
1020 }
1021
1022 if (found_prefix)
1023 {
1024 prefix = prefix_candidate;
1025 return true;
1026 }
1027
1028 return false;
1029 }
1030
1031 /// Return the version string of the library.
1032 ///
1033 /// @return the version string of the library.
1034 string
get_library_version_string()1035 get_library_version_string()
1036 {
1037 string major, minor, revision, version_string, suffix;
1038 abigail::abigail_get_library_version(major, minor, revision, suffix);
1039 version_string = major + "." + minor + "." + revision + suffix;
1040 return version_string;
1041 }
1042
1043 /// Return the version string for the ABIXML format.
1044 ///
1045 /// @return the version string of the ABIXML format.
1046 string
get_abixml_version_string()1047 get_abixml_version_string()
1048 {
1049 string major, minor, version_string;
1050 abigail::abigail_get_abixml_version(major, minor);
1051 version_string = major + "." + minor;
1052 return version_string;
1053 }
1054
1055 /// Execute a shell command and returns its output.
1056 ///
1057 /// @param cmd the shell command to execute.
1058 ///
1059 /// @param lines output parameter. This is set with the lines that
1060 /// constitute the output of the process that executed the command @p
1061 /// cmd.
1062 ///
1063 /// @return true iff the command was executed properly and no error
1064 /// was encountered.
1065 bool
execute_command_and_get_output(const string & cmd,vector<string> & lines)1066 execute_command_and_get_output(const string& cmd, vector<string>& lines)
1067 {
1068 if (cmd.empty())
1069 return false;
1070
1071 FILE *stream=
1072 popen(cmd.c_str(),
1073 /*open 'stream' in
1074 read-only mode: type=*/"r");
1075
1076 if (stream == NULL)
1077 return false;
1078
1079 string result;
1080
1081 #define TMP_BUF_LEN 1024 + 1
1082 char tmp_buf[TMP_BUF_LEN];
1083 memset(tmp_buf, 0, TMP_BUF_LEN);
1084
1085 while (fgets(tmp_buf, TMP_BUF_LEN, stream))
1086 {
1087 lines.push_back(tmp_buf);
1088 memset(tmp_buf, 0, TMP_BUF_LEN);
1089 }
1090
1091 if (pclose(stream) == -1)
1092 return false;
1093
1094 return true;
1095 }
1096
1097 /// Get the SONAMEs of the DSOs advertised as being "provided" by a
1098 /// given RPM. That set can be considered as being the set of
1099 /// "public" DSOs of the RPM.
1100 ///
1101 /// This runs the command "rpm -qp --provides <rpm> | grep .so" and
1102 /// filters its result.
1103 ///
1104 /// @param rpm_path the path to the RPM to consider.
1105 ///
1106 /// @param provided_dsos output parameter. This is set to the set of
1107 /// SONAMEs of the DSOs advertised as being provided by the RPM
1108 /// designated by @p rpm_path.
1109 ///
1110 /// @return true iff we could successfully query the RPM to see what
1111 /// DSOs it provides.
1112 bool
get_dsos_provided_by_rpm(const string & rpm_path,set<string> & provided_dsos)1113 get_dsos_provided_by_rpm(const string& rpm_path, set<string>& provided_dsos)
1114 {
1115 vector<string> query_output;
1116 // We don't check the return value of this command because on some
1117 // system, the command can issue errors but still emit a valid
1118 // output. We'll rather rely on the fact that the command emits a
1119 // valid output or not.
1120 execute_command_and_get_output("rpm -qp --provides "
1121 + rpm_path + " 2> /dev/null | grep .so",
1122 query_output);
1123
1124 for (vector<string>::const_iterator line = query_output.begin();
1125 line != query_output.end();
1126 ++line)
1127 {
1128 string dso = line->substr(0, line->find('('));
1129 dso = trim_white_space(dso);
1130 if (!dso.empty())
1131 provided_dsos.insert(dso);
1132 }
1133 return true;
1134 }
1135
1136 /// Remove spaces at the beginning and at the end of a given string.
1137 ///
1138 /// @param str the input string to consider.
1139 ///
1140 /// @return the @p str string with leading and trailing white spaces removed.
1141 string
trim_white_space(const string & str)1142 trim_white_space(const string& str)
1143 {
1144 if (str.empty())
1145 return "";
1146
1147 string result;
1148 string::size_type start, end;
1149 for (start = 0; start < str.length(); ++start)
1150 if (!isspace(str[start]))
1151 break;
1152
1153 for (end = str.length() - 1; end > 0; --end)
1154 if (!isspace(str[end]))
1155 break;
1156
1157 result = str.substr(start, end - start + 1);
1158 return result;
1159 }
1160
1161 /// Remove a string of pattern in front of a given string.
1162 ///
1163 /// For instance, consider this string:
1164 /// "../../../foo"
1165 ///
1166 /// The pattern "../" is repeated three times in front of the
1167 /// sub-string "foo". Thus, the call:
1168 /// trim_leading_string("../../../foo", "../")
1169 /// will return the string "foo".
1170 ///
1171 /// @param from the string to trim the leading repetition of pattern from.
1172 ///
1173 /// @param to_trim the pattern to consider (and to trim).
1174 ///
1175 /// @return the resulting string where the leading patter @p to_trim
1176 /// has been removed from.
1177 string
trim_leading_string(const string & from,const string & to_trim)1178 trim_leading_string(const string& from, const string& to_trim)
1179 {
1180 string str = from;
1181
1182 while (string_begins_with(str, to_trim))
1183 string_suffix(str, to_trim, str);
1184 return str;
1185 }
1186
1187 /// Convert a vector<char*> into a vector<char**>.
1188 ///
1189 /// @param char_stars the input vector.
1190 ///
1191 /// @param char_star_stars the output vector.
1192 void
convert_char_stars_to_char_star_stars(const vector<char * > & char_stars,vector<char ** > & char_star_stars)1193 convert_char_stars_to_char_star_stars(const vector<char*> &char_stars,
1194 vector<char**>& char_star_stars)
1195 {
1196 for (vector<char*>::const_iterator i = char_stars.begin();
1197 i != char_stars.end();
1198 ++i)
1199 char_star_stars.push_back(const_cast<char**>(&*i));
1200 }
1201
1202 /// The private data of the @ref temp_file type.
1203 struct temp_file::priv
1204 {
1205 char* path_template_;
1206 int fd_;
1207 shared_ptr<std::fstream> fstream_;
1208
privabigail::tools_utils::temp_file::priv1209 priv()
1210 {
1211 const char* templat = "/tmp/libabigail-tmp-file-XXXXXX";
1212 int s = strlen(templat);
1213 path_template_ = new char[s + 1];
1214 memset(path_template_, 0, s + 1);
1215 memcpy(path_template_, templat, s);
1216
1217 fd_ = mkstemp(path_template_);
1218 if (fd_ == -1)
1219 return;
1220
1221 fstream_.reset(new std::fstream(path_template_,
1222 std::ios::trunc
1223 | std::ios::in
1224 | std::ios::out));
1225 }
1226
~privabigail::tools_utils::temp_file::priv1227 ~priv()
1228 {
1229 if (fd_ && fd_ != -1)
1230 {
1231 fstream_.reset();
1232 close(fd_);
1233 remove(path_template_);
1234 }
1235 delete [] path_template_;
1236 }
1237 };
1238
1239 /// Default constructor of @ref temp_file.
1240 ///
1241 /// It actually creates the temporary file.
temp_file()1242 temp_file::temp_file()
1243 : priv_(new priv)
1244 {}
1245
1246 /// Test if the temporary file has been created and is usable.
1247 ///
1248 /// @return true iff the temporary file has been created and is
1249 /// useable.
1250 bool
is_good() const1251 temp_file::is_good() const
1252 {return priv_->fstream_->good();}
1253
1254 /// Return the path to the temporary file.
1255 ///
1256 /// @return the path to the temporary file if it's usable, otherwise
1257 /// return nil.
1258 const char*
get_path() const1259 temp_file::get_path() const
1260 {
1261 if (is_good())
1262 return priv_->path_template_;
1263
1264 return 0;
1265 }
1266
1267 /// Get the fstream to the temporary file.
1268 ///
1269 /// Note that the current process is aborted if this member function
1270 /// is invoked on an instance of @ref temp_file that is not usable.
1271 /// So please test that the instance is usable by invoking the
1272 /// temp_file::is_good() member function on it first.
1273 ///
1274 /// @return the fstream to the temporary file.
1275 std::fstream&
get_stream()1276 temp_file::get_stream()
1277 {
1278 ABG_ASSERT(is_good());
1279 return *priv_->fstream_;
1280 }
1281
1282 /// Create the temporary file and return it if it's usable.
1283 ///
1284 /// @return the newly created temporary file if it's usable, nil
1285 /// otherwise.
1286 temp_file_sptr
create()1287 temp_file::create()
1288 {
1289 temp_file_sptr result(new temp_file);
1290 if (result->is_good())
1291 return result;
1292
1293 return temp_file_sptr();
1294 }
1295
1296 /// Get a pseudo random number.
1297 ///
1298 /// @return a pseudo random number.
1299 size_t
get_random_number()1300 get_random_number()
1301 {
1302 static __thread bool initialized = false;
1303
1304 if (!initialized)
1305 {
1306 srand(time(NULL));
1307 initialized = true;
1308 }
1309
1310 return rand();
1311 }
1312
1313 /// Get a pseudo random number as string.
1314 ///
1315 /// @return a pseudo random number as string.
1316 string
get_random_number_as_string()1317 get_random_number_as_string()
1318 {
1319 std::ostringstream o;
1320 o << get_random_number();
1321
1322 return o.str();
1323 }
1324
1325 ostream&
operator <<(ostream & output,file_type r)1326 operator<<(ostream& output,
1327 file_type r)
1328 {
1329 string repr;
1330
1331 switch(r)
1332 {
1333 case FILE_TYPE_UNKNOWN:
1334 repr = "unknown file type";
1335 break;
1336 case FILE_TYPE_NATIVE_BI:
1337 repr = "native binary instrumentation file type";
1338 break;
1339 case FILE_TYPE_ELF:
1340 repr = "ELF file type";
1341 break;
1342 case FILE_TYPE_AR:
1343 repr = "archive file type";
1344 break;
1345 case FILE_TYPE_XML_CORPUS:
1346 repr = "native XML corpus file type";
1347 break;
1348 case FILE_TYPE_XML_CORPUS_GROUP:
1349 repr = "native XML corpus group file type";
1350 break;
1351 case FILE_TYPE_RPM:
1352 repr = "RPM file type";
1353 break;
1354 case FILE_TYPE_SRPM:
1355 repr = "SRPM file type";
1356 break;
1357 case FILE_TYPE_DEB:
1358 repr = "Debian binary file type";
1359 break;
1360 case FILE_TYPE_DIR:
1361 repr = "Directory type";
1362 break;
1363 case FILE_TYPE_TAR:
1364 repr = "GNU tar archive type";
1365 break;
1366 }
1367
1368 output << repr;
1369 return output;
1370 }
1371
1372 /// Guess the type of the content of an input stream.
1373 ///
1374 /// @param in the input stream to guess the content type for.
1375 ///
1376 /// @return the type of content guessed.
1377 file_type
guess_file_type(istream & in)1378 guess_file_type(istream& in)
1379 {
1380 const unsigned BUF_LEN = 264;
1381 const unsigned NB_BYTES_TO_READ = 263;
1382
1383 char buf[BUF_LEN];
1384 memset(buf, 0, BUF_LEN);
1385
1386 std::streampos initial_pos = in.tellg();
1387 in.read(buf, NB_BYTES_TO_READ);
1388 in.seekg(initial_pos);
1389
1390 if (in.gcount() < 4 || in.bad())
1391 return FILE_TYPE_UNKNOWN;
1392
1393 if (buf[0] == 0x7f
1394 && buf[1] == 'E'
1395 && buf[2] == 'L'
1396 && buf[3] == 'F')
1397 return FILE_TYPE_ELF;
1398
1399 if (buf[0] == '!'
1400 && buf[1] == '<'
1401 && buf[2] == 'a'
1402 && buf[3] == 'r'
1403 && buf[4] == 'c'
1404 && buf[5] == 'h'
1405 && buf[6] == '>')
1406 {
1407 if (strstr(buf, "debian-binary"))
1408 return FILE_TYPE_DEB;
1409 else
1410 return FILE_TYPE_AR;
1411 }
1412
1413 if (buf[0] == '<'
1414 && buf[1] == 'a'
1415 && buf[2] == 'b'
1416 && buf[3] == 'i'
1417 && buf[4] == '-'
1418 && buf[5] == 'i'
1419 && buf[6] == 'n'
1420 && buf[7] == 's'
1421 && buf[8] == 't'
1422 && buf[9] == 'r'
1423 && buf[10] == ' ')
1424 return FILE_TYPE_NATIVE_BI;
1425
1426 if (buf[0] == '<'
1427 && buf[1] == 'a'
1428 && buf[2] == 'b'
1429 && buf[3] == 'i'
1430 && buf[4] == '-'
1431 && buf[5] == 'c'
1432 && buf[6] == 'o'
1433 && buf[7] == 'r'
1434 && buf[8] == 'p'
1435 && buf[9] == 'u'
1436 && buf[10] == 's'
1437 && buf[11] == '-'
1438 && buf[12] == 'g'
1439 && buf[13] == 'r'
1440 && buf[14] == 'o'
1441 && buf[15] == 'u'
1442 && buf[16] == 'p'
1443 && buf[17] == ' ')
1444 return FILE_TYPE_XML_CORPUS_GROUP;
1445
1446 if (buf[0] == '<'
1447 && buf[1] == 'a'
1448 && buf[2] == 'b'
1449 && buf[3] == 'i'
1450 && buf[4] == '-'
1451 && buf[5] == 'c'
1452 && buf[6] == 'o'
1453 && buf[7] == 'r'
1454 && buf[8] == 'p'
1455 && buf[9] == 'u'
1456 && buf[10] == 's'
1457 && buf[11] == ' ')
1458 return FILE_TYPE_XML_CORPUS;
1459
1460 if ((unsigned char) buf[0] == 0xed
1461 && (unsigned char) buf[1] == 0xab
1462 && (unsigned char) buf[2] == 0xee
1463 && (unsigned char) buf[3] == 0xdb)
1464 {
1465 if (buf[7] == 0x00)
1466 return FILE_TYPE_RPM;
1467 else if (buf[7] == 0x01)
1468 return FILE_TYPE_SRPM;
1469 else
1470 return FILE_TYPE_UNKNOWN;
1471 }
1472
1473 if (buf[257] == 'u'
1474 && buf[258] == 's'
1475 && buf[259] == 't'
1476 && buf[260] == 'a'
1477 && buf[261] == 'r')
1478 return FILE_TYPE_TAR;
1479
1480 return FILE_TYPE_UNKNOWN;
1481 }
1482
1483 /// Guess the type of the content of an file.
1484 ///
1485 /// @param file_path the path to the file to consider.
1486 ///
1487 /// @return the type of content guessed.
1488 file_type
guess_file_type(const string & file_path)1489 guess_file_type(const string& file_path)
1490 {
1491 if (is_dir(file_path))
1492 return FILE_TYPE_DIR;
1493
1494 if (string_ends_with(file_path, ".tar")
1495 || string_ends_with(file_path, ".tar.gz")
1496 || string_ends_with(file_path, ".tgz")
1497 || string_ends_with(file_path, ".tar.bz2")
1498 || string_ends_with(file_path, ".tbz2")
1499 || string_ends_with(file_path, ".tbz")
1500 || string_ends_with(file_path, ".tb2")
1501 || string_ends_with(file_path, ".tar.xz")
1502 || string_ends_with(file_path, ".txz")
1503 || string_ends_with(file_path, ".tar.lzma")
1504 || string_ends_with(file_path, ".tar.lz")
1505 || string_ends_with(file_path, ".tlz")
1506 || string_ends_with(file_path, ".tar.Z")
1507 || string_ends_with(file_path, ".taz")
1508 || string_ends_with(file_path, ".tz"))
1509 return FILE_TYPE_TAR;
1510
1511 ifstream in(file_path.c_str(), ifstream::binary);
1512 file_type r = guess_file_type(in);
1513 in.close();
1514 return r;
1515 }
1516
1517 /// Get the package name of a .deb package.
1518 ///
1519 /// @param str the string containing the .deb NVR.
1520 ///
1521 /// @param name output parameter. This is set with the package name
1522 /// of the .deb package iff the function returns true.
1523 ///
1524 /// @return true iff the function successfully finds the .deb package
1525 /// name.
1526 bool
get_deb_name(const string & str,string & name)1527 get_deb_name(const string& str, string& name)
1528 {
1529 if (str.empty() || str[0] == '_')
1530 return false;
1531
1532 string::size_type str_len = str.length(), i = 0 ;
1533
1534 for (; i < str_len; ++i)
1535 {
1536 if (str[i] == '_')
1537 break;
1538 }
1539
1540 if (i == str_len)
1541 return false;
1542
1543 name = str.substr(0, i);
1544 return true;
1545 }
1546
1547 /// Get the package name of an rpm package.
1548 ///
1549 /// @param str the string containing the NVR of the rpm.
1550 ///
1551 /// @param name output parameter. This is set with the package name
1552 /// of the rpm package iff the function returns true.
1553 ///
1554 /// @return true iff the function successfully finds the rpm package
1555 /// name.
1556 bool
get_rpm_name(const string & str,string & name)1557 get_rpm_name(const string& str, string& name)
1558 {
1559 if (str.empty() || str[0] == '-')
1560 return false;
1561
1562 string::size_type str_len = str.length(), i = 0;
1563 string::value_type c;
1564
1565 for (; i < str_len; ++i)
1566 {
1567 c = str[i];
1568 string::size_type next_index = i + 1;
1569 if ((next_index < str_len) && c == '-' && isdigit(str[next_index]))
1570 break;
1571 }
1572
1573 if (i == str_len)
1574 return false;
1575
1576 name = str.substr(0, i);
1577
1578 return true;
1579 }
1580
1581 /// Get the architecture string from the NVR of an rpm.
1582 ///
1583 /// @param str the NVR to consider.
1584 ///
1585 /// @param arch output parameter. Is set to the resulting
1586 /// archirecture string iff the function returns true.
1587 ///
1588 /// @return true iff the function could find the architecture string
1589 /// from the NVR.
1590 bool
get_rpm_arch(const string & str,string & arch)1591 get_rpm_arch(const string& str, string& arch)
1592 {
1593 if (str.empty())
1594 return false;
1595
1596 if (!string_ends_with(str, ".rpm"))
1597 return false;
1598
1599 string::size_type str_len = str.length(), i = 0;
1600 string::value_type c;
1601 string::size_type last_dot_index = 0, dot_before_last_index = 0;
1602
1603 for (i = str_len - 1; i > 0; --i)
1604 {
1605 c = str[i];
1606 if (c == '.')
1607 {
1608 last_dot_index = i;
1609 break;
1610 }
1611 }
1612
1613 if (i == 0)
1614 return false;
1615
1616 for(--i; i > 0; --i)
1617 {
1618 c = str[i];
1619 if (c == '.')
1620 {
1621 dot_before_last_index = i;
1622 break;
1623 }
1624 }
1625
1626 if (i == 0)
1627 return false;
1628
1629 arch = str.substr(dot_before_last_index + 1,
1630 last_dot_index - dot_before_last_index - 1);
1631
1632 return true;
1633 }
1634
1635 /// Tests if a given file name designates a kernel package.
1636 ///
1637 /// @param file_name the file name to consider.
1638 ///
1639 /// @param file_type the type of the file @p file_name.
1640 ///
1641 /// @return true iff @p file_name of kind @p file_type designates a
1642 /// kernel package.
1643 bool
file_is_kernel_package(const string & file_name,file_type file_type)1644 file_is_kernel_package(const string& file_name, file_type file_type)
1645 {
1646 bool result = false;
1647 string package_name;
1648
1649 if (file_type == FILE_TYPE_RPM)
1650 {
1651 if (!get_rpm_name(file_name, package_name))
1652 return false;
1653 result = (package_name == "kernel");
1654 }
1655 else if (file_type == FILE_TYPE_DEB)
1656 {
1657 if (!get_deb_name(file_name, package_name))
1658 return false;
1659 result = (string_begins_with(package_name, "linux-image"));
1660 }
1661
1662 return result;
1663 }
1664
1665 /// Tests if a given file name designates a kernel debuginfo package.
1666 ///
1667 /// @param file_name the file name to consider.
1668 ///
1669 /// @param file_type the type of the file @p file_name.
1670 ///
1671 /// @return true iff @p file_name of kind @p file_type designates a
1672 /// kernel debuginfo package.
1673 bool
file_is_kernel_debuginfo_package(const string & file_name,file_type file_type)1674 file_is_kernel_debuginfo_package(const string& file_name, file_type file_type)
1675 {
1676 bool result = false;
1677 string package_name;
1678
1679 if (file_type == FILE_TYPE_RPM)
1680 {
1681 if (!get_rpm_name(file_name, package_name))
1682 return false;
1683 result = (package_name == "kernel-debuginfo");
1684 }
1685 else if (file_type == FILE_TYPE_DEB)
1686 {
1687 if (!get_deb_name(file_name, package_name))
1688 return false;
1689 result = (string_begins_with(package_name, "linux-image")
1690 && (string_ends_with(package_name, "-dbg")
1691 || string_ends_with(package_name, "-dbgsyms")));
1692 }
1693
1694 return result;
1695 }
1696
1697 /// The delete functor of a char buffer that has been created using
1698 /// malloc.
1699 struct malloced_char_star_deleter
1700 {
1701 void
operator ()abigail::tools_utils::malloced_char_star_deleter1702 operator()(char* ptr)
1703 {free(ptr);}
1704 };
1705
1706 /// Return a copy of the path given in argument, turning it into an
1707 /// absolute path by prefixing it with the concatenation of the result
1708 /// of get_current_dir_name() and the '/' character.
1709 ///
1710 /// The result being an shared_ptr to char*, it should manage its
1711 /// memory by itself and the user shouldn't need to wory too much for
1712 /// that.
1713 ///
1714 /// @param p the path to turn into an absolute path.
1715 ///
1716 /// @return a shared pointer to the resulting absolute path.
1717 std::shared_ptr<char>
make_path_absolute(const char * p)1718 make_path_absolute(const char*p)
1719 {
1720 using std::shared_ptr;
1721
1722 shared_ptr<char> result;
1723
1724 if (p && p[0] != '/')
1725 {
1726 shared_ptr<char> pwd(get_current_dir_name(),
1727 malloced_char_star_deleter());
1728 string s = string(pwd.get()) + "/" + p;
1729 result.reset(strdup(s.c_str()), malloced_char_star_deleter());
1730 }
1731 else
1732 result.reset(strdup(p), malloced_char_star_deleter());
1733
1734 return result;
1735 }
1736
1737 /// Return a copy of the path given in argument, turning it into an
1738 /// absolute path by prefixing it with the concatenation of the result
1739 /// of get_current_dir_name() and the '/' character.
1740 ///
1741 /// The result being a pointer to an allocated memory region, it must
1742 /// be freed by the caller.
1743 ///
1744 /// @param p the path to turn into an absolute path.
1745 ///
1746 /// @return a pointer to the resulting absolute path. It must be
1747 /// freed by the caller.
1748 char*
make_path_absolute_to_be_freed(const char * p)1749 make_path_absolute_to_be_freed(const char*p)
1750 {
1751 char* result = 0;
1752
1753 if (p && p[0] != '/')
1754 {
1755 char* pwd = get_current_dir_name();
1756 string s = string(pwd) + "/" + p;
1757 free(pwd);
1758 result = strdup(s.c_str());
1759 }
1760 else
1761 result = strdup(p);
1762
1763 return result;
1764 }
1765
1766 /// This is a sub-routine of gen_suppr_spec_from_headers and
1767 /// handle_fts_entry.
1768 ///
1769 /// It setups a type suppression which is meant to keep types defined
1770 /// in a given file and suppress all other types.
1771 ///
1772 /// @param file_path the path to the file that defines types that are
1773 /// meant to be kept by the type suppression. All other types defined
1774 /// in other files are to be suppressed. Note that this file path is
1775 /// added to the vector returned by
1776 /// type_suppression::get_source_locations_to_keep()
1777 ///
1778 /// @param suppr the type suppression to setup. If this smart pointer
1779 /// is nil then a new instance @ref type_suppression is created and
1780 /// this variable is made to point to it.
1781 static void
handle_file_entry(const string & file_path,type_suppression_sptr & suppr)1782 handle_file_entry(const string& file_path,
1783 type_suppression_sptr& suppr)
1784 {
1785 if (!suppr)
1786 {
1787 suppr.reset(new type_suppression(get_private_types_suppr_spec_label(),
1788 /*type_name_regexp=*/"",
1789 /*type_name=*/""));
1790
1791 // Types that are defined in system headers are usually
1792 // OK to be considered as public types.
1793 suppr->set_source_location_to_keep_regex_str("^/usr/include/");
1794 suppr->set_is_artificial(true);
1795 }
1796
1797 // And types that are defined in header files that are under
1798 // the header directory file we are looking are to be
1799 // considered public types too.
1800 suppr->get_source_locations_to_keep().insert(file_path);
1801 }
1802
1803 /// This is a sub-routine of gen_suppr_spec_from_headers.
1804 ///
1805 /// @param entry if this file represents a regular (or symlink) file,
1806 /// then its file name is going to be added to the vector returned by
1807 /// type_suppression::get_source_locations_to_keep().
1808 ///
1809 /// @param if @p entry represents a file, then its file name is going
1810 /// to be added to the vector returned by the method
1811 /// type_suppression::get_source_locations_to_keep of this instance.
1812 /// If this smart pointer is nil then a new instance @ref
1813 /// type_suppression is created and this variable is made to point to
1814 /// it.
1815 static void
handle_fts_entry(const FTSENT * entry,type_suppression_sptr & suppr)1816 handle_fts_entry(const FTSENT *entry,
1817 type_suppression_sptr& suppr)
1818 {
1819 if (entry == NULL
1820 || (entry->fts_info != FTS_F && entry->fts_info != FTS_SL)
1821 || entry->fts_info == FTS_ERR
1822 || entry->fts_info == FTS_NS)
1823 return;
1824
1825 string fname = entry->fts_name;
1826 if (!fname.empty())
1827 {
1828 if (string_ends_with(fname, ".h")
1829 || string_ends_with(fname, ".hpp")
1830 || string_ends_with(fname, ".hxx"))
1831 handle_file_entry (fname, suppr);
1832 }
1833 }
1834
1835 /// Populate a type_supression from header files found in a given
1836 /// directory tree.
1837 ///
1838 /// The suppression suppresses types defined in source files that are
1839 /// *NOT* found in the directory tree.
1840 ///
1841 /// This is a subroutine for gen_suppr_spect_from_headers.
1842 ///
1843 /// @param headers_root_dir the directory tree to consider for header
1844 /// files.
1845 ///
1846 /// @param result the type_supression to populate from the content of
1847 /// @p headers_root_dir.
1848 static void
gen_suppr_spec_from_headers_root_dir(const string & headers_root_dir,type_suppression_sptr & result)1849 gen_suppr_spec_from_headers_root_dir(const string& headers_root_dir,
1850 type_suppression_sptr &result)
1851 {
1852 if (!headers_root_dir.empty())
1853 {
1854 char* paths[] = {const_cast<char*>(headers_root_dir.c_str()), 0};
1855
1856 if (FTS *file_hierarchy = fts_open(paths, FTS_LOGICAL|FTS_NOCHDIR, NULL))
1857 {
1858 FTSENT *entry;
1859 while ((entry = fts_read(file_hierarchy)))
1860 handle_fts_entry(entry, result);
1861 fts_close(file_hierarchy);
1862 }
1863 }
1864 }
1865
1866 /// Generate a type suppression specification that suppresses ABI
1867 /// changes for types defined in source files that are neither in a
1868 /// given set of header root directories nor in a set of header
1869 /// files.
1870 ///
1871 /// @param headers_root_dirs ABI changes in types defined in files
1872 /// *NOT* found in these directory trees are going be suppressed.
1873 ///
1874 /// @param header_files a set of additional header files that define
1875 /// types that are to be kept (not supressed) by the returned type
1876 /// suppression.
1877 ///
1878 /// @return the resulting type suppression generated, if any file was
1879 /// found in the directory tree @p headers_root_dir.
1880 type_suppression_sptr
gen_suppr_spec_from_headers(const vector<string> & headers_root_dirs,const vector<string> & header_files)1881 gen_suppr_spec_from_headers(const vector<string>& headers_root_dirs,
1882 const vector<string>& header_files)
1883 {
1884 type_suppression_sptr result;
1885
1886 for (vector<string>::const_iterator root_dir = headers_root_dirs.begin();
1887 root_dir != headers_root_dirs.end();
1888 ++root_dir)
1889 gen_suppr_spec_from_headers_root_dir(*root_dir, result);
1890
1891 for (vector<string>::const_iterator file = header_files.begin();
1892 file != header_files.end();
1893 ++file)
1894 handle_file_entry(*file, result);
1895
1896 return result;
1897 }
1898
1899 /// Generate a type suppression specification that suppresses ABI
1900 /// changes for types defined in source files that are neither in a
1901 /// given header root dir, not in a set of header files.
1902 ///
1903 /// @param headers_root_dir ABI changes in types defined in files
1904 /// *NOT* found in this directory tree are going be suppressed.
1905 ///
1906 /// @param header_files a set of additional header files that define
1907 /// types that are to be kept (not supressed) by the returned type
1908 /// suppression.
1909 ///
1910 /// @return the resulting type suppression generated, if any file was
1911 /// found in the directory tree @p headers_root_dir.
1912 type_suppression_sptr
gen_suppr_spec_from_headers(const string & headers_root_dir,const vector<string> & header_files)1913 gen_suppr_spec_from_headers(const string& headers_root_dir,
1914 const vector<string>& header_files)
1915 {
1916 type_suppression_sptr result;
1917 vector<string> root_dirs;
1918
1919 if (!headers_root_dir.empty())
1920 root_dirs.push_back(headers_root_dir);
1921
1922 return gen_suppr_spec_from_headers(root_dirs, header_files);
1923 }
1924
1925 /// Generate a type suppression specification that suppresses ABI
1926 /// changes for types defined in source files that are not in a given
1927 /// header root dir.
1928 ///
1929 /// @param headers_root_dir ABI changes in types defined in files
1930 /// *NOT* found in this directory tree are going be suppressed.
1931 ///
1932 /// @return the resulting type suppression generated, if any file was
1933 /// found in the directory tree @p headers_root_dir.
1934 type_suppression_sptr
gen_suppr_spec_from_headers(const string & headers_root_dir)1935 gen_suppr_spec_from_headers(const string& headers_root_dir)
1936 {
1937 // We don't list individual files, just look under the headers_path.
1938 vector<string> header_files;
1939 return gen_suppr_spec_from_headers(headers_root_dir, header_files);
1940 }
1941
1942 /// Generate a suppression specification from kernel abi whitelist
1943 /// files.
1944 ///
1945 /// A kernel ABI whitelist file is an INI file that usually has only
1946 /// one section. The name of the section is a string that ends up
1947 /// with the sub-string "symbol_list" or "whitelist". For instance
1948 /// RHEL7_x86_64_symbol_list.
1949 ///
1950 /// Then the content of the section is a set of function or variable
1951 /// names, one name per line. Each function or variable name is the
1952 /// name of a function or a variable whose changes are to be keept.
1953 ///
1954 /// A whitelist file can have multiple sections (adhering to the naming
1955 /// conventions and multiple files can be passed. The suppression that
1956 /// is created takes all whitelist sections from all files into account.
1957 /// Symbols (or expression of such) are deduplicated in the final
1958 /// suppression expression.
1959 ///
1960 /// This function reads the white lists and generates a
1961 /// function_suppression_sptr and variable_suppression_sptr and returns
1962 /// a vector containing those.
1963 ///
1964 /// @param abi_whitelist_paths a vector of KMI whitelist paths
1965 ///
1966 /// @return a vector or suppressions
1967 suppressions_type
gen_suppr_spec_from_kernel_abi_whitelists(const std::vector<std::string> & abi_whitelist_paths)1968 gen_suppr_spec_from_kernel_abi_whitelists
1969 (const std::vector<std::string>& abi_whitelist_paths)
1970 {
1971
1972 std::vector<std::string> whitelisted_names;
1973 for (std::vector<std::string>::const_iterator
1974 path_iter = abi_whitelist_paths.begin(),
1975 path_end = abi_whitelist_paths.end();
1976 path_iter != path_end;
1977 ++path_iter)
1978 {
1979
1980 abigail::ini::config whitelist;
1981 if (!read_config(*path_iter, whitelist))
1982 continue;
1983
1984 const ini::config::sections_type& whitelist_sections =
1985 whitelist.get_sections();
1986
1987 for (ini::config::sections_type::const_iterator
1988 section_iter = whitelist_sections.begin(),
1989 section_end = whitelist_sections.end();
1990 section_iter != section_end;
1991 ++section_iter)
1992 {
1993 std::string section_name = (*section_iter)->get_name();
1994 if (!string_ends_with(section_name, "symbol_list")
1995 && !string_ends_with(section_name, "whitelist"))
1996 continue;
1997 for (ini::config::properties_type::const_iterator
1998 prop_iter = (*section_iter)->get_properties().begin(),
1999 prop_end = (*section_iter)->get_properties().end();
2000 prop_iter != prop_end;
2001 ++prop_iter)
2002 {
2003 if (const simple_property_sptr& prop =
2004 is_simple_property(*prop_iter))
2005 if (prop->has_empty_value())
2006 {
2007 const std::string& name = prop->get_name();
2008 if (!name.empty())
2009 whitelisted_names.push_back(name);
2010 }
2011 }
2012 }
2013 }
2014
2015 suppressions_type result;
2016 if (!whitelisted_names.empty())
2017 {
2018 // Drop duplicates to simplify the regex we are generating
2019 std::sort(whitelisted_names.begin(), whitelisted_names.end());
2020 whitelisted_names.erase(std::unique(whitelisted_names.begin(),
2021 whitelisted_names.end()),
2022 whitelisted_names.end());
2023
2024 // Build a regular expression representing the union of all
2025 // the function and variable names expressed in the white list.
2026 const std::string regex = regex::generate_from_strings(whitelisted_names);
2027
2028 // Build a suppression specification which *keeps* functions
2029 // whose ELF symbols match the regular expression contained
2030 // in function_names_regexp. This will also keep the ELF
2031 // symbols (not designated by any debug info) whose names
2032 // match this regexp.
2033 function_suppression_sptr fn_suppr(new function_suppression);
2034 fn_suppr->set_label("whitelist");
2035 fn_suppr->set_symbol_name_not_regex_str(regex);
2036 fn_suppr->set_drops_artifact_from_ir(true);
2037 result.push_back(fn_suppr);
2038
2039 // Build a suppression specification which *keeps* variables
2040 // whose ELF symbols match the regular expression contained
2041 // in function_names_regexp. This will also keep the ELF
2042 // symbols (not designated by any debug info) whose names
2043 // match this regexp.
2044 variable_suppression_sptr var_suppr(new variable_suppression);
2045 var_suppr->set_label("whitelist");
2046 var_suppr->set_symbol_name_not_regex_str(regex);
2047 var_suppr->set_drops_artifact_from_ir(true);
2048 result.push_back(var_suppr);
2049 }
2050 return result;
2051 }
2052
2053 /// Get the path to the default system suppression file.
2054 ///
2055 /// @return a copy of the default system suppression file.
2056 string
get_default_system_suppression_file_path()2057 get_default_system_suppression_file_path()
2058 {
2059 string default_system_suppr_path;
2060
2061 const char *s = getenv("LIBABIGAIL_DEFAULT_SYSTEM_SUPPRESSION_FILE");
2062 if (s)
2063 default_system_suppr_path = s;
2064
2065 if (default_system_suppr_path.empty())
2066 default_system_suppr_path =
2067 get_system_libdir() + string("/libabigail/default.abignore");
2068
2069 return default_system_suppr_path;
2070 }
2071
2072 /// Get the path to the default user suppression file.
2073 ///
2074 /// @return a copy of the default user suppression file.
2075 string
get_default_user_suppression_file_path()2076 get_default_user_suppression_file_path()
2077 {
2078 string default_user_suppr_path;
2079 const char *s = getenv("LIBABIGAIL_DEFAULT_USER_SUPPRESSION_FILE");
2080
2081 if (s == NULL)
2082 {
2083 s = getenv("HOME");
2084 if (s == NULL)
2085 return "";
2086 default_user_suppr_path = s;
2087 if (default_user_suppr_path.empty())
2088 default_user_suppr_path = "~";
2089 default_user_suppr_path += "/.abignore";
2090 }
2091 else
2092 default_user_suppr_path = s;
2093
2094 return default_user_suppr_path;
2095 }
2096
2097 /// Load the default system suppression specification file and
2098 /// populate a vector of @ref suppression_sptr with its content.
2099 ///
2100 /// The default system suppression file is located at
2101 /// $libdir/libabigail/default-libabigail.abignore.
2102 ///
2103 /// @param supprs the vector to add the suppression specifications
2104 /// read from the file to.
2105 void
load_default_system_suppressions(suppr::suppressions_type & supprs)2106 load_default_system_suppressions(suppr::suppressions_type& supprs)
2107 {
2108 string default_system_suppr_path =
2109 get_default_system_suppression_file_path();
2110
2111 read_suppressions(default_system_suppr_path, supprs);
2112 }
2113
2114 /// Load the default user suppression specification file and populate
2115 /// a vector of @ref suppression_sptr with its content.
2116 ///
2117 /// The default user suppression file is located at $HOME~/.abignore.
2118 ///
2119 /// @param supprs the vector to add the suppression specifications
2120 /// read from the file to.
2121 void
load_default_user_suppressions(suppr::suppressions_type & supprs)2122 load_default_user_suppressions(suppr::suppressions_type& supprs)
2123 {
2124 string default_user_suppr_path =
2125 get_default_user_suppression_file_path();
2126
2127 read_suppressions(default_user_suppr_path, supprs);
2128 }
2129
2130 /// Test if a given FTSENT* denotes a file with a given name.
2131 ///
2132 /// @param entry the FTSENT* to consider.
2133 ///
2134 /// @param fname the file name (or end of path) to consider.
2135 ///
2136 /// @return true iff @p entry denotes a file which path ends with @p
2137 /// fname.
2138 static bool
entry_of_file_with_name(const FTSENT * entry,const string & fname)2139 entry_of_file_with_name(const FTSENT *entry,
2140 const string& fname)
2141 {
2142 if (entry == NULL
2143 || (entry->fts_info != FTS_F && entry->fts_info != FTS_SL)
2144 || entry->fts_info == FTS_ERR
2145 || entry->fts_info == FTS_NS)
2146 return false;
2147
2148 string fpath = ::basename(entry->fts_path);
2149 if (fpath == fname)
2150 return true;
2151 return false;
2152 }
2153
2154 /// Find a given file under a root directory and return its absolute
2155 /// path.
2156 ///
2157 /// @param root_dir the root directory under which to look for.
2158 ///
2159 /// @param file_path_to_look_for the file to look for under the
2160 /// directory @p root_dir.
2161 ///
2162 /// @param result the resulting path to @p file_path_to_look_for.
2163 /// This is set iff the file has been found.
2164 bool
find_file_under_dir(const string & root_dir,const string & file_path_to_look_for,string & result)2165 find_file_under_dir(const string& root_dir,
2166 const string& file_path_to_look_for,
2167 string& result)
2168 {
2169 char* paths[] = {const_cast<char*>(root_dir.c_str()), 0};
2170
2171 FTS *file_hierarchy = fts_open(paths,
2172 FTS_PHYSICAL|FTS_NOCHDIR|FTS_XDEV, 0);
2173 if (!file_hierarchy)
2174 return false;
2175
2176 FTSENT *entry;
2177 while ((entry = fts_read(file_hierarchy)))
2178 {
2179 // Skip descendents of symbolic links.
2180 if (entry->fts_info == FTS_SL || entry->fts_info == FTS_SLNONE)
2181 {
2182 fts_set(file_hierarchy, entry, FTS_SKIP);
2183 continue;
2184 }
2185 if (entry_of_file_with_name(entry, file_path_to_look_for))
2186 {
2187 result = entry->fts_path;
2188 return true;
2189 }
2190 }
2191
2192 fts_close(file_hierarchy);
2193 return false;
2194 }
2195 /// If we were given suppression specification files or kabi whitelist
2196 /// files, this function parses those, come up with suppression
2197 /// specifications as a result, and set them to the read context.
2198 ///
2199 /// @param read_ctxt the read context to consider.
2200 ///
2201 /// @param suppr_paths paths to suppression specification files that
2202 /// we were given. If empty, it means we were not given any
2203 /// suppression specification path.
2204 ///
2205 /// @param kabi_whitelist_paths paths to kabi whitelist files that we
2206 /// were given. If empty, it means we were not given any kabi
2207 /// whitelist.
2208 ///
2209 /// @param supprs the suppressions specifications resulting from
2210 /// parsing the suppression specification files at @p suppr_paths and
2211 /// the kabi whitelist at @p kabi_whitelist_paths.
2212 ///
2213 /// @param opts the options to consider.
2214 static void
load_generate_apply_suppressions(dwarf_reader::read_context & read_ctxt,vector<string> & suppr_paths,vector<string> & kabi_whitelist_paths,suppressions_type & supprs)2215 load_generate_apply_suppressions(dwarf_reader::read_context &read_ctxt,
2216 vector<string>& suppr_paths,
2217 vector<string>& kabi_whitelist_paths,
2218 suppressions_type& supprs)
2219 {
2220 if (supprs.empty())
2221 {
2222 for (vector<string>::const_iterator i = suppr_paths.begin();
2223 i != suppr_paths.end();
2224 ++i)
2225 read_suppressions(*i, supprs);
2226
2227 const suppressions_type& wl_suppr =
2228 gen_suppr_spec_from_kernel_abi_whitelists(kabi_whitelist_paths);
2229
2230 supprs.insert(supprs.end(), wl_suppr.begin(), wl_suppr.end());
2231 }
2232
2233 abigail::dwarf_reader::add_read_context_suppressions(read_ctxt, supprs);
2234 }
2235
2236 /// Test if an FTSENT pointer (resulting from fts_read) represents the
2237 /// vmlinux binary.
2238 ///
2239 /// @param entry the FTSENT to consider.
2240 ///
2241 /// @return true iff @p entry is for a vmlinux binary.
2242 static bool
is_vmlinux(const FTSENT * entry)2243 is_vmlinux(const FTSENT *entry)
2244 {
2245 if (entry == NULL
2246 || (entry->fts_info != FTS_F && entry->fts_info != FTS_SL)
2247 || entry->fts_info == FTS_ERR
2248 || entry->fts_info == FTS_NS)
2249 return false;
2250
2251 string fname = entry->fts_name;
2252
2253 if (fname == "vmlinux")
2254 {
2255 string dirname;
2256 dir_name(entry->fts_path, dirname);
2257 if (string_ends_with(dirname, "compressed"))
2258 return false;
2259
2260 return true;
2261 }
2262
2263 return false;
2264 }
2265
2266 /// Test if an FTSENT pointer (resulting from fts_read) represents a a
2267 /// linux kernel module binary.
2268 ///
2269 /// @param entry the FTSENT to consider.
2270 ///
2271 /// @return true iff @p entry is for a linux kernel module binary.
2272 static bool
is_kernel_module(const FTSENT * entry)2273 is_kernel_module(const FTSENT *entry)
2274 {
2275 if (entry == NULL
2276 || (entry->fts_info != FTS_F && entry->fts_info != FTS_SL)
2277 || entry->fts_info == FTS_ERR
2278 || entry->fts_info == FTS_NS)
2279 return false;
2280
2281 string fname = entry->fts_name;
2282 if (string_ends_with(fname, ".ko")
2283 || string_ends_with(fname, ".ko.xz")
2284 || string_ends_with(fname, ".ko.gz"))
2285 return true;
2286
2287 return false;
2288 }
2289
2290 /// Find a vmlinux and its kernel modules in a given directory tree.
2291 ///
2292 /// @param from the directory tree to start looking from.
2293 ///
2294 /// @param vmlinux_path output parameter. This is set to the path
2295 /// where the vmlinux binary is found. This is set iff the returns
2296 /// true and if this argument was empty to begin with.
2297 ///
2298 /// @param module_paths output parameter. This is set to the paths of
2299 /// the linux kernel module binaries.
2300 ///
2301 /// @return true iff at least the vmlinux binary was found.
2302 static bool
find_vmlinux_and_module_paths(const string & from,string & vmlinux_path,vector<string> & module_paths)2303 find_vmlinux_and_module_paths(const string& from,
2304 string &vmlinux_path,
2305 vector<string> &module_paths)
2306 {
2307 char* path[] = {const_cast<char*>(from.c_str()), 0};
2308
2309 FTS *file_hierarchy = fts_open(path, FTS_PHYSICAL|FTS_NOCHDIR|FTS_XDEV, 0);
2310 if (!file_hierarchy)
2311 return false;
2312
2313 bool found_vmlinux = !vmlinux_path.empty();
2314 FTSENT *entry;
2315 while ((entry = fts_read(file_hierarchy)))
2316 {
2317 // Skip descendents of dead symbolic links.
2318 if (entry->fts_info == FTS_SLNONE)
2319 {
2320 fts_set(file_hierarchy, entry, FTS_SKIP);
2321 continue;
2322 }
2323
2324 if (!found_vmlinux && is_vmlinux(entry))
2325 {
2326 vmlinux_path = entry->fts_path;
2327 found_vmlinux = true;
2328 }
2329 else if (is_kernel_module(entry))
2330 module_paths.push_back(entry->fts_path);
2331 }
2332
2333 fts_close(file_hierarchy);
2334
2335 return found_vmlinux;
2336 }
2337
2338 /// Find a vmlinux binary in a given directory tree.
2339 ///
2340 /// @param from the directory tree to start looking from.
2341 ///
2342 /// @param vmlinux_path output parameter
2343 ///
2344 /// return true iff the vmlinux binary was found
2345 static bool
find_vmlinux_path(const string & from,string & vmlinux_path)2346 find_vmlinux_path(const string& from,
2347 string &vmlinux_path)
2348 {
2349 char* path[] = {const_cast<char*>(from.c_str()), 0};
2350
2351 FTS *file_hierarchy = fts_open(path, FTS_PHYSICAL|FTS_NOCHDIR|FTS_XDEV, 0);
2352 if (!file_hierarchy)
2353 return false;
2354
2355 bool found_vmlinux = false;
2356 FTSENT *entry;
2357 while ((entry = fts_read(file_hierarchy)))
2358 {
2359 // Skip descendents of symbolic links.
2360 if (entry->fts_info == FTS_SL || entry->fts_info == FTS_SLNONE)
2361 {
2362 fts_set(file_hierarchy, entry, FTS_SKIP);
2363 continue;
2364 }
2365
2366 if (!found_vmlinux && is_vmlinux(entry))
2367 {
2368 vmlinux_path = entry->fts_path;
2369 found_vmlinux = true;
2370 break;
2371 }
2372 }
2373
2374 fts_close(file_hierarchy);
2375
2376 return found_vmlinux;
2377 }
2378
2379 /// Get the paths of the vmlinux and kernel module binaries under
2380 /// given directory.
2381 ///
2382 /// @param dist_root the directory under which to look for.
2383 ///
2384 /// @param debug_info_root_path the path to the directory under which
2385 /// debug info is going to be found for binaries under @p dist_root.
2386 ///
2387 /// @param vmlinux_path output parameter. The path of the vmlinux
2388 /// binary that was found.
2389 ///
2390 /// @param module_paths output parameter. The paths of the kernel
2391 /// module binaries that were found, sorted to impose a deterministic
2392 /// ordering.
2393 ///
2394 /// @return true if at least the path to the vmlinux binary was found.
2395 bool
get_binary_paths_from_kernel_dist(const string & dist_root,const string & debug_info_root_path,string & vmlinux_path,vector<string> & module_paths)2396 get_binary_paths_from_kernel_dist(const string& dist_root,
2397 const string& debug_info_root_path,
2398 string& vmlinux_path,
2399 vector<string>& module_paths)
2400 {
2401 if (!dir_exists(dist_root))
2402 return false;
2403
2404 // For now, we assume either an Enterprise Linux or a Fedora kernel
2405 // distribution directory.
2406 //
2407 // We also take into account split debug info package for these. In
2408 // this case, the content split debug info package is installed
2409 // under the 'debug_info_root_path' directory and its content is
2410 // accessible from <debug_info_root_path>/usr/lib/debug directory.
2411
2412 string kernel_modules_root;
2413 string debug_info_root;
2414 if (dir_exists(dist_root + "/lib/modules"))
2415 {
2416 dist_root + "/lib/modules";
2417 debug_info_root = debug_info_root_path.empty()
2418 ? dist_root
2419 : debug_info_root_path;
2420 debug_info_root += "/usr/lib/debug";
2421 }
2422
2423 if (dir_is_empty(debug_info_root))
2424 debug_info_root.clear();
2425
2426 bool found = false;
2427 string from = dist_root;
2428 if (find_vmlinux_and_module_paths(from, vmlinux_path, module_paths))
2429 found = true;
2430
2431 std::sort(module_paths.begin(), module_paths.end());
2432
2433 return found;
2434 }
2435
2436 /// Get the path of the vmlinux binary under the given directory, that
2437 /// must have been generated either from extracting a package.
2438 ///
2439 /// @param from the directory under which to look for.
2440 ///
2441 /// @param vmlinux_path output parameter. The path of the vmlinux
2442 /// binary that was found.
2443 ///
2444 /// @return true if the path to the vmlinux binary was found.
2445 bool
get_vmlinux_path_from_kernel_dist(const string & from,string & vmlinux_path)2446 get_vmlinux_path_from_kernel_dist(const string& from,
2447 string& vmlinux_path)
2448 {
2449 if (!dir_exists(from))
2450 return false;
2451
2452 // For now, we assume the possibility of having either an Enterprise
2453 // Linux or a Fedora kernel distribution directory. In those cases,
2454 // the vmlinux binary is located under the /lib/modules
2455 // sub-directory. So we might as well save some time by picking it
2456 // from there directly.
2457
2458 string dist_root = from;
2459 if (dir_exists(dist_root + "/lib/modules"))
2460 dist_root += "/lib/modules";
2461
2462 bool found = false;
2463 if (find_vmlinux_path(dist_root, vmlinux_path))
2464 found = true;
2465
2466 return found;
2467 }
2468
2469 /// Get the paths of the vmlinux and kernel module binaries under
2470 /// given directory.
2471 ///
2472 /// @param dist_root the directory under which to look for.
2473 ///
2474 /// @param vmlinux_path output parameter. The path of the vmlinux
2475 /// binary that was found.
2476 ///
2477 /// @param module_paths output parameter. The paths of the kernel
2478 /// module binaries that were found.
2479 ///
2480 /// @return true if at least the path to the vmlinux binary was found.
2481 bool
get_binary_paths_from_kernel_dist(const string & dist_root,string & vmlinux_path,vector<string> & module_paths)2482 get_binary_paths_from_kernel_dist(const string& dist_root,
2483 string& vmlinux_path,
2484 vector<string>& module_paths)
2485 {
2486 string debug_info_root_path;
2487 return get_binary_paths_from_kernel_dist(dist_root,
2488 debug_info_root_path,
2489 vmlinux_path,
2490 module_paths);
2491 }
2492
2493 /// If the @ref origin is DWARF_ORIGIN it build a @ref corpus_group
2494 /// made of vmlinux kernel file and the linux kernel modules found
2495 /// under @p root directory and under its sub-directories, recursively.
2496 ///
2497 /// @param origin the debug type information in vmlinux kernel and
2498 /// the linux kernel modules to be used to build the corpora @p group.
2499 ///
2500 /// @param the group @ref corpus_group to be built.
2501 ///
2502 /// @param vmlinux the path to the vmlinux binary.
2503 ///
2504 /// @param modules a vector with the paths to the linux kernel
2505 /// modules.
2506 ///
2507 /// @param root the path of the directory under which the kernel
2508 /// kernel modules were found.
2509 ///
2510 /// @param di_root the directory in aboslute path which debug
2511 /// info is to be found for binaries under director @p root
2512 ///
2513 /// @param suppr_paths the paths to the suppression specifications to
2514 /// apply while loading the binaries.
2515 ///
2516 /// @param kabi_wl_path the paths to the kabi whitelist files to take
2517 /// into account while loading the binaries.
2518 ///
2519 /// @param supprs the suppressions resulting from parsing the
2520 /// suppression specifications at @p suppr_paths. This is set by this
2521 /// function.
2522 ///
2523 /// @param verbose true if the function has to emit some verbose
2524 /// messages.
2525 ///
2526 /// @param t time to trace time spent in each step.
2527 ///
2528 /// @param env the environment to create the corpus_group in.
2529 static void
maybe_load_vmlinux_dwarf_corpus(corpus::origin origin,corpus_group_sptr & group,const string & vmlinux,vector<string> & modules,const string & root,vector<char ** > & di_roots,vector<string> & suppr_paths,vector<string> & kabi_wl_paths,suppressions_type & supprs,bool verbose,timer & t,environment_sptr & env)2530 maybe_load_vmlinux_dwarf_corpus(corpus::origin origin,
2531 corpus_group_sptr& group,
2532 const string& vmlinux,
2533 vector<string>& modules,
2534 const string& root,
2535 vector<char**>& di_roots,
2536 vector<string>& suppr_paths,
2537 vector<string>& kabi_wl_paths,
2538 suppressions_type& supprs,
2539 bool verbose,
2540 timer& t,
2541 environment_sptr& env)
2542 {
2543 if (!(origin & corpus::DWARF_ORIGIN))
2544 return;
2545
2546 abigail::elf_reader::status status = abigail::elf_reader::STATUS_OK;
2547 dwarf_reader::read_context_sptr ctxt;
2548 ctxt =
2549 dwarf_reader::create_read_context(vmlinux, di_roots, env.get(),
2550 /*read_all_types=*/false,
2551 /*linux_kernel_mode=*/true);
2552 dwarf_reader::set_do_log(*ctxt, verbose);
2553
2554 t.start();
2555 load_generate_apply_suppressions(*ctxt, suppr_paths,
2556 kabi_wl_paths, supprs);
2557 t.stop();
2558
2559 if (verbose)
2560 std::cerr << "loaded white list and generated suppr spec in: "
2561 << t
2562 << "\n";
2563
2564 group.reset(new corpus_group(env.get(), root));
2565
2566 set_read_context_corpus_group(*ctxt, group);
2567
2568 if (verbose)
2569 std::cerr << "reading kernel binary '"
2570 << vmlinux << "' ...\n" << std::flush;
2571
2572 // Read the vmlinux corpus and add it to the group.
2573 t.start();
2574 read_and_add_corpus_to_group_from_elf(*ctxt, *group, status);
2575 t.stop();
2576
2577 if (verbose)
2578 std::cerr << vmlinux
2579 << " reading DONE:"
2580 << t << "\n";
2581
2582 if (group->is_empty())
2583 return;
2584
2585 // Now add the corpora of the modules to the corpus group.
2586 int total_nb_modules = modules.size();
2587 int cur_module_index = 1;
2588 for (vector<string>::const_iterator m = modules.begin();
2589 m != modules.end();
2590 ++m, ++cur_module_index)
2591 {
2592 if (verbose)
2593 std::cerr << "reading module '"
2594 << *m << "' ("
2595 << cur_module_index
2596 << "/" << total_nb_modules
2597 << ") ... " << std::flush;
2598
2599 reset_read_context(ctxt, *m, di_roots, env.get(),
2600 /*read_all_types=*/false,
2601 /*linux_kernel_mode=*/true);
2602
2603 load_generate_apply_suppressions(*ctxt, suppr_paths,
2604 kabi_wl_paths, supprs);
2605
2606 set_read_context_corpus_group(*ctxt, group);
2607
2608 t.start();
2609 read_and_add_corpus_to_group_from_elf(*ctxt,
2610 *group, status);
2611 t.stop();
2612 if (verbose)
2613 std::cerr << "module '"
2614 << *m
2615 << "' reading DONE: "
2616 << t << "\n";
2617 }
2618 }
2619
2620 /// If the @ref origin is CTF_ORIGIN it build a @ref corpus_group
2621 /// made of vmlinux kernel file and the linux kernel modules found
2622 /// under @p root directory and under its sub-directories, recursively.
2623 ///
2624 /// @param origin the debug type information in vmlinux kernel and
2625 /// the linux kernel modules to be used to build the corpora @p group.
2626 ///
2627 /// @param group the @ref corpus_group to be built.
2628 ///
2629 /// @param vmlinux the path to the vmlinux binary.
2630 ///
2631 /// @param modules a vector with the paths to the linux kernel
2632 /// modules.
2633 ///
2634 /// @param root the path of the directory under which the kernel
2635 /// kernel modules were found.
2636 ///
2637 /// @param di_root the directory in aboslute path which debug
2638 /// info is to be found for binaries under director @p root
2639 ///
2640 /// @param verbose true if the function has to emit some verbose
2641 /// messages.
2642 ///
2643 /// @param t time to trace time spent in each step.
2644 ///
2645 /// @param env the environment to create the corpus_group in.
2646 #ifdef WITH_CTF
2647 static void
maybe_load_vmlinux_ctf_corpus(corpus::origin origin,corpus_group_sptr & group,const string & vmlinux,vector<string> & modules,const string & root,vector<char ** > & di_roots,bool verbose,timer & t,environment_sptr & env)2648 maybe_load_vmlinux_ctf_corpus(corpus::origin origin,
2649 corpus_group_sptr& group,
2650 const string& vmlinux,
2651 vector<string>& modules,
2652 const string& root,
2653 vector<char**>& di_roots,
2654 bool verbose,
2655 timer& t,
2656 environment_sptr& env)
2657 {
2658 if (!(origin & corpus::CTF_ORIGIN))
2659 return;
2660
2661 abigail::elf_reader::status status = abigail::elf_reader::STATUS_OK;
2662 ctf_reader::read_context_sptr ctxt;
2663 ctxt = ctf_reader::create_read_context(vmlinux, di_roots, env.get());
2664 group.reset(new corpus_group(env.get(), root));
2665 set_read_context_corpus_group(*ctxt, group);
2666
2667 if (verbose)
2668 std::cerr << "reading kernel binary '"
2669 << vmlinux << "' ...\n" << std::flush;
2670
2671 // Read the vmlinux corpus and add it to the group.
2672 t.start();
2673 read_and_add_corpus_to_group_from_elf(ctxt.get(), *group, status);
2674 t.stop();
2675
2676 if (verbose)
2677 std::cerr << vmlinux
2678 << " reading DONE:"
2679 << t << "\n";
2680
2681 if (group->is_empty())
2682 return;
2683
2684 // Now add the corpora of the modules to the corpus group.
2685 int total_nb_modules = modules.size();
2686 int cur_module_index = 1;
2687 for (vector<string>::const_iterator m = modules.begin();
2688 m != modules.end();
2689 ++m, ++cur_module_index)
2690 {
2691 if (verbose)
2692 std::cerr << "reading module '"
2693 << *m << "' ("
2694 << cur_module_index
2695 << "/" << total_nb_modules
2696 << ") ... " << std::flush;
2697
2698 reset_read_context(ctxt, *m, di_roots, env.get());
2699 set_read_context_corpus_group(*ctxt, group);
2700
2701 t.start();
2702 read_and_add_corpus_to_group_from_elf(ctxt.get(),
2703 *group, status);
2704 t.stop();
2705 if (verbose)
2706 std::cerr << "module '"
2707 << *m
2708 << "' reading DONE: "
2709 << t << "\n";
2710 }
2711 }
2712 #endif
2713
2714 /// Walk a given directory and build an instance of @ref corpus_group
2715 /// from the vmlinux kernel binary and the linux kernel modules found
2716 /// under that directory and under its sub-directories, recursively.
2717 ///
2718 /// The main corpus of the @ref corpus_group is made of the vmlinux
2719 /// binary. The other corpora are made of the linux kernel binaries.
2720 ///
2721 /// Depending of the @ref origin it delegates the corpus build @p group
2722 /// to:
2723 /// @ref maybe_load_vmlinux_dwarf_corpus
2724 /// @ref maybe_load_vmlinux_ctf_corpus
2725 ///
2726 /// @param root the path of the directory under which the kernel
2727 /// kernel modules are to be found. The vmlinux can also be found
2728 /// somewhere under that directory, but if it's not in there, its path
2729 /// can be set to the @p vmlinux_path parameter.
2730 ///
2731 /// @param debug_info_root the directory under which debug info is to
2732 /// be found for binaries under director @p root.
2733 ///
2734 /// @param vmlinux_path the path to the vmlinux binary, if that binary
2735 /// is not under the @p root directory. If this is empty, then it
2736 /// means the vmlinux binary is to be found under the @p root
2737 /// directory.
2738 ///
2739 /// @param suppr_paths the paths to the suppression specifications to
2740 /// apply while loading the binaries.
2741 ///
2742 /// @param kabi_wl_path the paths to the kabi whitelist files to take
2743 /// into account while loading the binaries.
2744 ///
2745 /// @param supprs the suppressions resulting from parsing the
2746 /// suppression specifications at @p suppr_paths. This is set by this
2747 /// function.
2748 ///
2749 /// @param verbose true if the function has to emit some verbose
2750 /// messages.
2751 ///
2752 /// @param env the environment to create the corpus_group in.
2753 corpus_group_sptr
build_corpus_group_from_kernel_dist_under(const string & root,const string debug_info_root,const string & vmlinux_path,vector<string> & suppr_paths,vector<string> & kabi_wl_paths,suppressions_type & supprs,bool verbose,environment_sptr & env,corpus::origin origin)2754 build_corpus_group_from_kernel_dist_under(const string& root,
2755 const string debug_info_root,
2756 const string& vmlinux_path,
2757 vector<string>& suppr_paths,
2758 vector<string>& kabi_wl_paths,
2759 suppressions_type& supprs,
2760 bool verbose,
2761 environment_sptr& env,
2762 corpus::origin origin)
2763 {
2764 string vmlinux = vmlinux_path;
2765 corpus_group_sptr group;
2766 vector<string> modules;
2767
2768 if (verbose)
2769 std::cerr << "Analysing kernel dist root '"
2770 << root << "' ... " << std::flush;
2771
2772 timer t;
2773
2774 t.start();
2775 bool got_binary_paths =
2776 get_binary_paths_from_kernel_dist(root, debug_info_root, vmlinux, modules);
2777 t.stop();
2778
2779 if (verbose)
2780 std::cerr << "DONE: " << t << "\n";
2781
2782 if (got_binary_paths)
2783 {
2784 shared_ptr<char> di_root =
2785 make_path_absolute(debug_info_root.c_str());
2786 char *di_root_ptr = di_root.get();
2787 vector<char**> di_roots;
2788 di_roots.push_back(&di_root_ptr);
2789
2790 maybe_load_vmlinux_dwarf_corpus(origin, group, vmlinux,
2791 modules, root, di_roots,
2792 suppr_paths, kabi_wl_paths,
2793 supprs, verbose, t, env);
2794 #ifdef WITH_CTF
2795 maybe_load_vmlinux_ctf_corpus(origin, group, vmlinux,
2796 modules, root, di_roots,
2797 verbose, t, env);
2798 #endif
2799 }
2800
2801 return group;
2802 }
2803
2804 }//end namespace tools_utils
2805
2806 using abigail::ir::function_decl;
2807
2808 /// Dump (to the standard error stream) two sequences of strings where
2809 /// each string represent one of the functions in the two sequences of
2810 /// functions given in argument to this function.
2811 ///
2812 /// @param a_begin the begin iterator for the first input sequence of
2813 /// functions.
2814 ///
2815 /// @parm a_end the end iterator for the first input sequence of
2816 /// functions.
2817 ///
2818 /// @param b_begin the begin iterator for the second input sequence of
2819 /// functions.
2820 ///
2821 /// @param b_end the end iterator for the second input sequence of functions.
2822 void
dump_functions_as_string(std::vector<function_decl * >::const_iterator a_begin,std::vector<function_decl * >::const_iterator a_end,std::vector<function_decl * >::const_iterator b_begin,std::vector<function_decl * >::const_iterator b_end)2823 dump_functions_as_string(std::vector<function_decl*>::const_iterator a_begin,
2824 std::vector<function_decl*>::const_iterator a_end,
2825 std::vector<function_decl*>::const_iterator b_begin,
2826 std::vector<function_decl*>::const_iterator b_end)
2827 {abigail::fns_to_str(a_begin, a_end, b_begin, b_end, std::cerr);}
2828
2829 /// Dump (to the standard error output stream) a pretty representation
2830 /// of the signatures of two sequences of functions.
2831 ///
2832 /// @param a_begin the start iterator of the first input sequence of functions.
2833 ///
2834 /// @param a_end the end iterator of the first input sequence of functions.
2835 ///
2836 /// @param b_begin the start iterator of the second input sequence of functions.
2837 ///
2838 /// @param b_end the end iterator of the second input sequence of functions.
2839 void
dump_function_names(std::vector<function_decl * >::const_iterator a_begin,std::vector<function_decl * >::const_iterator a_end,std::vector<function_decl * >::const_iterator b_begin,std::vector<function_decl * >::const_iterator b_end)2840 dump_function_names(std::vector<function_decl*>::const_iterator a_begin,
2841 std::vector<function_decl*>::const_iterator a_end,
2842 std::vector<function_decl*>::const_iterator b_begin,
2843 std::vector<function_decl*>::const_iterator b_end)
2844 {
2845 std::vector<function_decl*>::const_iterator i;
2846 std::ostream& o = std::cerr;
2847 for (i = a_begin; i != a_end; ++i)
2848 o << (*i)->get_pretty_representation() << "\n";
2849
2850 o << " ->|<- \n";
2851 for (i = b_begin; i != b_end; ++i)
2852 o << (*i)->get_pretty_representation() << "\n";
2853 o << "\n";
2854 }
2855
2856 /// Compare two functions that are in a vector of functions.
2857 ///
2858 /// @param an iterator to the beginning of the the sequence of functions.
2859 ///
2860 /// @param f1_index the index of the first function to compare.
2861 ///
2862 /// @param f2_inde the index of the second function to compare
2863 bool
compare_functions(vector<function_decl * >::const_iterator base,unsigned f1_index,unsigned f2_index)2864 compare_functions(vector<function_decl*>::const_iterator base,
2865 unsigned f1_index, unsigned f2_index)
2866 {
2867 function_decl* fn1 = base[f1_index];
2868 function_decl* fn2 = base[f2_index];
2869
2870 return *fn1 == *fn2;
2871 }
2872
2873 }//end namespace abigail
2874