[4/4] gdb/dap: only include sourceReference if file path does not exist

Message ID 20230901180323.22328-11-greg@gpanders.com
State New
Headers
Series DAP fixups |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 success Testing passed
linaro-tcwg-bot/tcwg_gdb_build--master-arm success Testing passed
linaro-tcwg-bot/tcwg_gdb_check--master-aarch64 fail Testing failed
linaro-tcwg-bot/tcwg_gdb_check--master-arm pending Test started

Commit Message

Gregory Anders Sept. 1, 2023, 5:55 p.m. UTC
  According to the DAP specification if the "sourceReference" field is
included in a Source object and is non-zero, then the DAP client _must_
make a "source" request to the debugger to retrieve file contents, even
if the Source object also includes path information.

If the Source's path field is a valid path that the DAP client is able
to read from the filesystem, having to make another request to the
debugger to get the file contents is wasteful and leads to incorrect
results (DAP clients will try to get the contents from the server and
display those contents as a file with the name in "source.path", but
this could conflict with the _acutal_ existing file at "source.path").

Instead, only set "sourceReference" if the source file path does not
exist.
---

It is unclear exactly what the meaning of "sourceReference" is from the
specification, but at least two items support my interpretation here:

1. The wording in the specification for sourceReference mentions that
   the contents of the source must be retrieved through a 'source'
   request, _even if_ a path is specified. This suggests to me that
   sourceReference is meant to be used in cases where the debugger has
   access to source contents that the client does not (e.g. I could see
   something like this being used for "constructed" or "generated"
   sources, like disassembly).

2. Other debuggers (such as lldb-vscode and codelldb) do not set the
   sourceReference field at all for files which exist on disk. If this
   patch is accepted then GDB will match the behavior of those two.

And at least anecdotally, my testing with nvim-dap (my DAP client of
choice) shows that this behavior better matches the expectation of that
DAP client in particular.

 gdb/python/lib/gdb/dap/sources.py | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)
  

Comments

Tom Tromey Sept. 1, 2023, 7:42 p.m. UTC | #1
>>>>> "Gregory" == Gregory Anders via Gdb-patches <gdb-patches@sourceware.org> writes:

Thank you for the patch.

Gregory> According to the DAP specification if the "sourceReference" field is
Gregory> included in a Source object and is non-zero, then the DAP client _must_
Gregory> make a "source" request to the debugger to retrieve file contents, even
Gregory> if the Source object also includes path information.

On closer reading (this isn't the first time I've gotten the DAP spec
wrong) I think you are right.

The docs for the Source object say:

   * If the value > 0 the contents of the source must be retrieved through the
   * `source` request (even if a path is specified).

... which I think supports your view.

Gregory> +        if not os.path.exists(fullname):
Gregory> +            global _next_source

I suspect we should just remove all sourceReference handling from
sources.py, because if we can't find a full path, gdb isn't going to be
able to come up with the source anyway.

What do you think?

I thought the test suite might need some updates but I don't see any
references to sourceReference there.

Tom
  
Gregory Anders Sept. 1, 2023, 9:42 p.m. UTC | #2
On Fri, 01 Sep 2023 13:42 -0600, Tom Tromey wrote:
>I suspect we should just remove all sourceReference handling from
>sources.py, because if we can't find a full path, gdb isn't going to be
>able to come up with the source anyway.
>
>What do you think?

I agree. I avoided doing this originally to keep the change small, but I
think eliminating sourceReference completely (for the reason you
mentioned) is the better approach. It will keep the code simpler.

>I thought the test suite might need some updates but I don't see any
>references to sourceReference there.

There was one test case that was failing with the v1 patch
(sources.exp). I updated it in v2 (sent a few minutes ago).

Greg
  
Gregory Anders Sept. 1, 2023, 9:56 p.m. UTC | #3
On Fri, 01 Sep 2023 21:43 +0000, Gregory Anders wrote:
>On Fri, 01 Sep 2023 13:42 -0600, Tom Tromey wrote:
>>I suspect we should just remove all sourceReference handling from
>>sources.py, because if we can't find a full path, gdb isn't going to be
>>able to come up with the source anyway.
>>
>>What do you think?
>
>I agree. I avoided doing this originally to keep the change small, but I
>think eliminating sourceReference completely (for the reason you
>mentioned) is the better approach. It will keep the code simpler.
>
>>I thought the test suite might need some updates but I don't see any
>>references to sourceReference there.
>
>There was one test case that was failing with the v1 patch
>(sources.exp). I updated it in v2 (sent a few minutes ago).
>
>Greg

I checked what lldb-vscode does, and AFAICT they are doing the same
thing proposed in this patch: if the source file exists/is valid, they
do not populate sourceReference at all and do not maintain that entry
in any kind of source map.

They do, however, maintain a source map (with sourceReferences) for
things like function disassembly. So it might be worth keeping that
stuff around in GDB in case that feature is ever implemented in the
future.

Your call.

Greg
  

Patch

diff --git a/gdb/python/lib/gdb/dap/sources.py b/gdb/python/lib/gdb/dap/sources.py
index 7fa1ae43..00a70701 100644
--- a/gdb/python/lib/gdb/dap/sources.py
+++ b/gdb/python/lib/gdb/dap/sources.py
@@ -13,6 +13,8 @@ 
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+import os
+
 import gdb
 
 from .server import request, capability
@@ -41,16 +43,20 @@  def make_source(fullname, filename):
     if fullname in _source_map:
         result = _source_map[fullname]
     else:
-        global _next_source
         result = {
             "name": filename,
             "path": fullname,
-            "sourceReference": _next_source,
         }
+
+        if not os.path.exists(fullname):
+            global _next_source
+            result["sourceReference"] = _next_source
+
+            global _id_map
+            _id_map[_next_source] = result
+            _next_source += 1
+
         _source_map[fullname] = result
-        global _id_map
-        _id_map[_next_source] = result
-        _next_source += 1
     return result