Repository: qir-alliance/pyqir (language: python)

Issue:
Passing a null pointer to extract_byte_string causes Python to segfault
**Describe the bug**

`extract_byte_string` causes Python to segfault if its argument is a Value representing a null pointer.

**To Reproduce**

```
from pyqir import Context, Module, extract_byte_string

llvm_ir = """
define void @program_main() {
entry:
  %0 = add i64 1, 2
  call void @__quantum__rt__int_record_output(i64 %0, i8* null)
  ret void
}

declare void @__quantum__rt__int_record_output(i64, i8*)
"""

module = Module.from_ir(Context(), llvm_ir, "module")
null_ptr_value = module.functions[0].basic_blocks[0].instructions[1].operands[1]
x = extract_byte_string(null_ptr_value)
```

**Expected behavior**

I would expect `extract_byte_string` to return None or raise an exception in this case.

**System information**

* pyqir version 0.8.1
* python version 3.9.6




Discussion / hints:

Fix patch:
diff --git a/eng/psakefile.ps1 b/eng/psakefile.ps1
index 2ac5d2e7..aedb5d20 100644
--- a/eng/psakefile.ps1
+++ b/eng/psakefile.ps1
@@ -22,7 +22,7 @@ Properties {
 task default -depends build, run-examples
 task build -depends qirlib, pyqir
 task checks -depends cargo-fmt, cargo-clippy, black, mypy
-task manylinux -depends build-manylinux-container-image, run-manylinux-container-image, run-examples-in-containers 
+task manylinux -depends build-manylinux-container-image, run-manylinux-container-image, run-examples-in-containers
 
 task run-manylinux-container-image -preaction { Write-CacheStats } -postaction { Write-CacheStats } {
     $llvmDir = Resolve-InstallationDirectory
@@ -125,7 +125,7 @@ task check-environment {
     }
 
     Assert ((Test-InVirtualEnvironment) -eq $true) ($env_message -Join ' ')
-    exec { & $Python -m pip install pip~=22.3 }
+    exec { & $Python -m pip install pip~=23.1 }
 }
 
 task init -depends check-environment {
@@ -320,7 +320,7 @@ task run-examples {
         & $Python bernstein_vazirani.py | Tee-Object -Variable bz_output
         $bz_lines = $bz_output -join ", "
         $bz_expected = "x(5), h(0), h(1), h(2), h(3), h(4), h(5), cnot(1, 5), cnot(3, 5), cnot(4, 5), h(0), h(1), h(2), h(3), h(4), mz(0, 0), mz(1, 1), mz(2, 2), mz(3, 3), mz(4, 4)"
-        
+
         Assert (@(Compare-Object $bz_lines $bz_expected).Length -eq 0) "Expected $bz_expected found $bz_lines"
 
         & $Python teleport.py | Tee-Object -Variable teleport_output
diff --git a/qirlib/src/values.rs b/qirlib/src/values.rs
index 1660cb72..c2f516d7 100644
--- a/qirlib/src/values.rs
+++ b/qirlib/src/values.rs
@@ -154,6 +154,10 @@ pub unsafe fn global_string(module: LLVMModuleRef, value: &[u8]) -> LLVMValueRef
 }
 
 pub unsafe fn extract_string(value: LLVMValueRef) -> Option<Vec<u8>> {
+    if LLVMIsNull(value) != 0 {
+        return None;
+    }
+
     if !is_byte_string(LLVMTypeOf(value)) {
         return None;
     }


Test patch:
diff --git a/pyqir/tests/test_parser.py b/pyqir/tests/test_parser.py
index d803f2a4..9d588d10 100644
--- a/pyqir/tests/test_parser.py
+++ b/pyqir/tests/test_parser.py
@@ -138,6 +138,21 @@ def test_global_string() -> None:
     assert value.decode("utf-8") == "Hello World!\0"
 
 
+def test_null_i8ptr_string() -> None:
+    llvm_ir = """
+    define void @main() {
+      call void @a(i8* null)
+      ret void
+    }
+    declare void @a(i8*)
+    """
+
+    module = Module.from_ir(Context(), llvm_ir, "module")
+    null_ptr_value = module.functions[0].basic_blocks[0].instructions[0].operands[0]
+    string = extract_byte_string(null_ptr_value)
+    assert string is None
+
+
 def test_parser_zext_support() -> None:
     bitcode = Path("tests/select.bc").read_bytes()
     mod = Module.from_bitcode(Context(), bitcode)