Repository: nvaccess/nvda

Issue:
No item names for some listviews (64-bit)
### Steps to reproduce:
Example app: Tortoise SVN

- invoke a context command such as SVN Update on a local repo.
- A window appears with the progress made so far.
- Tab to the list and try to navigate.
- one or multiple  error sounds are issued.

### Expected behavior:
The name of the focused item should be announced.

### Actual behavior:
Only the state (e.g. selected) is announced

### System configuration:
NVDA version next-15028,2754a2c8
Installed/Source/portable

Windows 10 (x64), Version 1803 (OS Build 17133.73)

Name and version of other software in use when reproducing the issue:
Tortoise SVN 1.9.7.27907 x64
### Other questions:

Does the issue still occur after restarting your PC?

Yes

Have you tried any other versions of NVDA?

Not relevant



Discussion:
I am also very uncomfortable with this problem. This issue can be reproduced in both 2018.1 and master, next versions.
## Background
The list in question is defined in:

> NVDAObjects.IAccessible.sysListView32

The problematic class is:

> ListItemWithoutColumnSupport(IAccessible)

and the method involved:

> _getColumnContentRaw(self, index)

### General Code Flow:

- virtual memory is allocated in the 64-bit  process
- One part is for the receiving buffer
- the other part is for the LVITEM, a special structure that is sent with the message
- watchdog.cancellableSendMessage is called with msg = LVM_GETITEMTEXTW and the structure mentioned above
- the listview item now holds a pointer to the item name
- NVDA makes an attempt to read the buffer
- fail

### Debug Result
Note that this is made with NVDA in source mode and two helper functions:

- a Repr class added to the LVITEM structure to show its members at each step
- Printout of the arguments that are passed to "winKernel.readProcessMemory"

The command (after selecting an item in the list view) is:

	>>> focus._getColumnContentRaw( <index> )

#### 1. The structure written to VM:
		mask 513 # set text and columns
		iItem 0
		iSubItem 0
		state 0
		stateMask 0
		pszText 57671680 # desired address of the receiving buffer
		cchTextMax 512 # length
		iImage 0
		lParam 0
		iIndent 0
		iGroupID 0
		cColumns 0
		puColumns 0
		piColFmt 0
		iGroup 0

#### 2. the arguments passed to "ReadProcessMemory":

		2372 <type 'int'> # Process Handle
		57606144 <type 'int'> # Base Address
		<cparam 'P' (06AE9DB8)> <type 'CArgObject'> # the structure
		80 <type 'int'> # size in bytes
		None <type 'NoneType'>

#### 3. The returned structure 

		mask 1 # Text
		iItem 0
		iSubItem 0
		state 0
		stateMask 0
		pszText 669217843296 # didn't use our address!!
		cchTextMax 512 # length was accepted though
		iImage 0
		lParam 0
		iIndent 0
		iGroupID 0
		cColumns 0
		puColumns 0
		piColFmt 0
		iGroup 0

#### 4. The arguments for reading the buffer:

		2372 <type 'int'>
		669217843296 <type 'long'> # Note that int is now long which is actually c_ulonglong
		<ctypes.c_wchar_Array_1 object at 0x06CDC490> <class 'ctypes.c_wchar_Array_1'>
		2 <type 'int'>
		None <type 'NoneType'>

#### 5. The resulting error:

		Traceback (most recent call last):
		  File "<console>", line 1, in <module>
		  File "C:\nvda\source\NVDAObjects\IAccessible\sysListView32.py", line 341, in _getColumnContentRaw
			winKernel.readProcessMemory(processHandle,item.pszText,buffer,sizeof(buffer),None)
		  File "C:\nvda\source\winKernel.py", line 180, in readProcessMemory
			return kernel32.ReadProcessMemory(*args)
		ArgumentError: argument 2: <type 'exceptions.OverflowError'>: long int too long to convert


### Conclusion:
- The listview control doesn't accept our pointer to the buffer and replaces it with a 64-bit pointer.
- From our 32-bit process, it is impossible to read the buffer.

Note that all other functions in this class that use the similar procedure work fine.
So can the column headers be read without problems.

There is nothing mentioned on MSDN with regard to the LVM_GETITEMTEXTW message.

However, this can be found under  the similar LVM_GETITEMW "Remarks" section:

> If the LVIF_TEXT flag is set in the mask member of the LVITEM structure, the pszText member must point to a valid buffer and the cchTextMax member must be set to the number of characters in that buffer.  
> Applications should not assume that the text will necessarily be placed in the specified buffer.  
> The control may instead change the pszText member of the structure to point to the new text, rather than place it in the buffer.

<https://msdn.microsoft.com/en-us/library/windows/desktop/bb774953(v=vs.85).aspx>
I just closed #7874 in favour of this issue. @Robert-J-H: Thanks for providing a technical background.
Hi,

Because we did have issues with this particular structure, and since we now have more detailed information, I recommend keeping this one in favor of others. Thanks. CC @JCSTeh, @MichaelDCurran
Thanks @leonardder

I should have investigated a bit deeper and commented on the open issue.
However, the problem is more general and affects probably all 64-bit applications that use this class (but I've only encountered Tortoise).
Note that there is another bug hidden in the method.
If you look at the arguments under 4.0, there is the integer "2" right above "None".
This is the buffer length, however, this can't be since we are using 512 characters, no matter what.
Notice how the variable len (bad name by the way) is used to create the buffer within the relevant code snippet:

```
len = watchdog.cancellableSendMessage(self.windowHandle,LVM_GETITEMTEXTW, (self.IAccessibleChildID-1), internalItem)
				if len:
					winKernel.readProcessMemory(processHandle,internalItem,byref(item),sizeof(self.LVITEM),None)
					buffer=create_unicode_buffer(len)
					winKernel.readProcessMemory(processHandle,item.pszText,buffer,sizeof(buffer),None)

```

For the buffer creation "item.cchTextMax" should be used instead.
LVM_GETITEMTEXT is documented to return the length of the text, which is why we do (and it's appropriate for us to) use it as the length of the buffer. The issue with 64 bit apps is that NVDA, as a 32 bit app, can't accept remote 64 bit memory addresses. There's another issue open somewhere to deal with this using our own in-process memory reading/writing helpers.

@jcsteh
Thanks James,
I've just realized that len is the amount of characters.
The _getHeaderRaw method does it curiously in the other way:
```
	res = watchdog.cancellableSendMessage(self.windowHandle,LVM_GETCOLUMNW, index, internalColumn)
	if res:
		winKernel.readProcessMemory(processHandle,internalColumn,byref(column),sizeof(self.LVCOLUMN),None)
		buffer=create_unicode_buffer(column.cchTextMax)
		winKernel.readProcessMemory(processHandle,column.pszText,buffer,sizeof(buffer),None)```

I agree with you, an external Helper function is probably needed.
I first contemplated replacing the text pointer with a callback (-1) but this would only move the problem to another place.
[Using text callbacks in ListView Controls](https://www.codeproject.com/Articles/70/Using-text-callbacks-in-ListView-Controls)
I've created pull request #8190 that addresses this issue, although not at a IPC level.
I think that it would be embarrassing if we recommended Tortoise SVN for translators without proper accessibility for the program.
I hope that some of you have other programs that exhibit the same bug and can be tested with the workaround.
I'm using NVDA version alpha-15693,b46b83ea and now the reading of the log list in TortoiseSVN is broken again. Was this commit one of the ones lost during the change of the testing/release process a few weeks ago?
Hi, yes. Thanks.
If I read the two above comments correctly, shouldn't the patch for PR #8190 be merged again or am I missing the point?
I don't understand why it was lost and never merged again...
Sure, it could have been merged some two years ago.
Seems that NV Access wants another way but I'm not sure if it will
work so easily within the C++ code.

On 13/05/2020, ruifontes <notifications@github.com> wrote:
> I don't understand why it was lost and never merged again...
>
> --
> You are receiving this because you were mentioned.
> Reply to this email directly or view it on GitHub:
> https://github.com/nvaccess/nvda/issues/8175#issuecomment-627666290

Bumping this because it's also been identified as a show-stopper issue for Sibiac (an NVDA addon that provides access to DAW plugins in context of music production). Any news on that merge?
cc @michaelDCurran, @feerrenrut 

Resolving patch:
diff --git a/source/NVDAObjects/IAccessible/sysListView32.py b/source/NVDAObjects/IAccessible/sysListView32.py
--- a/source/NVDAObjects/IAccessible/sysListView32.py
+++ b/source/NVDAObjects/IAccessible/sysListView32.py
@@ -1,9 +1,7 @@
-# -*- coding: UTF-8 -*-
-#NVDAObjects/IAccessible/sysListView32.py
-#A part of NonVisual Desktop Access (NVDA)
-#Copyright (C) 2006-2017 NV Access Limited, Peter Vágner
-#This file is covered by the GNU General Public License.
-#See the file COPYING for more details.
+# A part of NonVisual Desktop Access (NVDA)
+# Copyright (C) 2006-2020 NV Access Limited, Peter Vágner, Leonard de Ruijter
+# This file is covered by the GNU General Public License.
+# See the file COPYING for more details.
 
 import time
 from ctypes import *
@@ -359,6 +357,21 @@ def _getColumnLocation(self,column):
 		return self._getColumnLocationRaw(self.parent._columnOrderArray[column - 1])
 
 	def _getColumnContentRaw(self, index):
+		item = self.IAccessibleChildID - 1
+		subItem = index
+		text = AutoFreeBSTR()
+		if watchdog.cancellableExecute(
+			NVDAHelper.localLib.nvdaInProcUtils_sysListView32_getColumnContent,
+			self.appModule.helperLocalBindingHandle,
+			self.windowHandle,
+			item,
+			subItem,
+			ctypes.byref(text)
+		) != 0:
+			return None
+		return text.value
+
+	def _getColumnContentRawOutProc(self, index):
 		buffer=None
 		processHandle=self.processHandle
 		internalItem=winKernel.virtualAllocEx(processHandle,None,sizeof(self.LVITEM),winKernel.MEM_COMMIT,winKernel.PAGE_READWRITE)
@@ -379,7 +392,10 @@ def _getColumnContentRaw(self, index):
 		return buffer.value if buffer else None
 
 	def _getColumnContent(self, column):
-		return self._getColumnContentRaw(self.parent._columnOrderArray[column - 1])
+		index = self.parent._columnOrderArray[column - 1]
+		if not self.appModule.helperLocalBindingHandle:
+			return self._getColumnContentRawOutProc(index)
+		return self._getColumnContentRaw(index)
 
 	def _getColumnImageIDRaw(self, index):
 		processHandle=self.processHandle


Test patch: