1/* Find line information for given file/line/column triple.
2   Copyright (C) 2005-2009 Red Hat, Inc.
3   This file is part of elfutils.
4   Written by Ulrich Drepper <drepper@redhat.com>, 2005.
5
6   This file is free software; you can redistribute it and/or modify
7   it under the terms of either
8
9     * the GNU Lesser General Public License as published by the Free
10       Software Foundation; either version 3 of the License, or (at
11       your option) any later version
12
13   or
14
15     * the GNU General Public License as published by the Free
16       Software Foundation; either version 2 of the License, or (at
17       your option) any later version
18
19   or both in parallel, as here.
20
21   elfutils is distributed in the hope that it will be useful, but
22   WITHOUT ANY WARRANTY; without even the implied warranty of
23   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
24   General Public License for more details.
25
26   You should have received copies of the GNU General Public License and
27   the GNU Lesser General Public License along with this program.  If
28   not, see <http://www.gnu.org/licenses/>.  */
29
30#ifdef HAVE_CONFIG_H
31# include <config.h>
32#endif
33
34#include <assert.h>
35#include <limits.h>
36#include <stdlib.h>
37#include <string.h>
38
39#include "libdwP.h"
40
41
42int
43dwarf_getsrc_file (Dwarf *dbg, const char *fname, int lineno, int column,
44		   Dwarf_Line ***srcsp, size_t *nsrcs)
45{
46  if (dbg == NULL)
47    return -1;
48
49  bool is_basename = strchr (fname, '/') == NULL;
50
51  size_t max_match = *nsrcs ?: ~0u;
52  size_t act_match = *nsrcs;
53  size_t cur_match = 0;
54  Dwarf_Line **match = *nsrcs == 0 ? NULL : *srcsp;
55
56  size_t cuhl;
57  Dwarf_Off noff;
58  for (Dwarf_Off off = 0;
59       INTUSE(dwarf_nextcu) (dbg, off, &noff, &cuhl, NULL, NULL, NULL) == 0;
60       off = noff)
61    {
62      Dwarf_Die cudie_mem;
63      Dwarf_Die *cudie = INTUSE(dwarf_offdie) (dbg, off + cuhl, &cudie_mem);
64      if (cudie == NULL)
65	continue;
66
67      /* Get the line number information for this file.  */
68      Dwarf_Lines *lines;
69      size_t nlines;
70      if (INTUSE(dwarf_getsrclines) (cudie, &lines, &nlines) != 0)
71	{
72	  /* Ignore a CU that just has no DW_AT_stmt_list at all.  */
73	  int error = INTUSE(dwarf_errno) ();
74	  if (error == 0)
75	    continue;
76	  __libdw_seterrno (error);
77	  return -1;
78	}
79
80      /* Search through all the line number records for a matching
81	 file and line/column number.  If any of the numbers is zero,
82	 no match is performed.  */
83      unsigned int lastfile = UINT_MAX;
84      bool lastmatch = false;
85      for (size_t cnt = 0; cnt < nlines; ++cnt)
86	{
87	  Dwarf_Line *line = &lines->info[cnt];
88
89	  if (lastfile != line->file)
90	    {
91	      lastfile = line->file;
92	      if (lastfile >= line->files->nfiles)
93		{
94		  __libdw_seterrno (DWARF_E_INVALID_DWARF);
95		  return -1;
96		}
97
98	      /* Match the name with the name the user provided.  */
99	      const char *fname2 = line->files->info[lastfile].name;
100	      if (is_basename)
101		lastmatch = strcmp (basename (fname2), fname) == 0;
102	      else
103		lastmatch = strcmp (fname2, fname) == 0;
104	    }
105	  if (!lastmatch)
106	    continue;
107
108	  /* See whether line and possibly column match.  */
109	  if (lineno != 0
110	      && (lineno > line->line
111		  || (column != 0 && column > line->column)))
112	    /* Cannot match.  */
113	    continue;
114
115	  /* Determine whether this is the best match so far.  */
116	  size_t inner;
117	  for (inner = 0; inner < cur_match; ++inner)
118	    if (match[inner]->files == line->files
119		&& match[inner]->file == line->file)
120	      break;
121	  if (inner < cur_match
122	      && (match[inner]->line != line->line
123		  || match[inner]->line != lineno
124		  || (column != 0
125		      && (match[inner]->column != line->column
126			  || match[inner]->column != column))))
127	    {
128	      /* We know about this file already.  If this is a better
129		 match for the line number, use it.  */
130	      if (match[inner]->line >= line->line
131		  && (match[inner]->line != line->line
132		      || match[inner]->column >= line->column))
133		/*  Use the new line.  Otherwise the old one.  */
134		match[inner] = line;
135	      continue;
136	    }
137
138	  if (cur_match < max_match)
139	    {
140	      if (cur_match == act_match)
141		{
142		  /* Enlarge the array for the results.  */
143		  act_match += 10;
144		  Dwarf_Line **newp = realloc (match,
145					       act_match
146					       * sizeof (Dwarf_Line *));
147		  if (newp == NULL)
148		    {
149		      free (match);
150		      __libdw_seterrno (DWARF_E_NOMEM);
151		      return -1;
152		    }
153		  match = newp;
154		}
155
156	      match[cur_match++] = line;
157	    }
158	}
159
160      /* If we managed to find as many matches as the user requested
161	 already, there is no need to go on to the next CU.  */
162      if (cur_match == max_match)
163	break;
164    }
165
166  if (cur_match > 0)
167    {
168      assert (*nsrcs == 0 || *srcsp == match);
169
170      *nsrcs = cur_match;
171      *srcsp = match;
172
173      return 0;
174    }
175
176  __libdw_seterrno (DWARF_E_NO_MATCH);
177  return -1;
178}
179