Repository: Qiskit/qiskit

Issue:
cmd_def parameter string is incorrectly reported as unsafe
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues -->

### Information

- **Qiskit Terra version**:
- **Python version**:
- **Operating system**:

### What is the current behavior?
When parsing the cmd_def provided by the backend, pulse commands may be parameterized (typically phase shifts). These commands are sanitized before being passed to Sympy for parameter and expression extraction. Currently, this sanitization is too conservative and is failing on the string ```"-(P1)"```.


### Steps to reproduce the problem
Create a cmd_def with a frame change command with the parameter ```"-(P1)"```.


### What is the expected behavior?
This string should pass sanitization


### Suggested solutions
Relax sanitization strictness to allow consecutive special match characters. In the long run, replace with a full math parser.




Discussion:

Resolving patch:
diff --git a/qiskit/qobj/converters/pulse_instruction.py b/qiskit/qobj/converters/pulse_instruction.py
--- a/qiskit/qobj/converters/pulse_instruction.py
+++ b/qiskit/qobj/converters/pulse_instruction.py
@@ -227,7 +227,9 @@ def convert_snapshot(self, shift, instruction):
 _math_ops_regex = r"(" + ")|(".join(_math_ops) + ")"
 # match consecutive alphanumeric, and single consecutive math ops +-/.()
 # and multiple * for exponentiation
-_allowedchars = re.compile(r'([+\/\-\(\)\.])?([\sa-zA-Z\d]+[+\/\-\(\)\.]?\*{0,2})*')
+_allowedchars = re.compile(r'(([+\/\-]?|\*{0,2})?[\(\)\s]*'  # allow to start with math/bracket
+                           r'([a-zA-Z][a-zA-Z\d]*|'  # match word
+                           r'[\d]+(\.\d*)?)[\(\)\s]*)*')  # match decimal and bracket
 # match any sequence of chars and numbers
 _expr_regex = r'([a-zA-Z]+\d*)'
 # and valid params
@@ -263,6 +265,7 @@ def _is_math_expr_safe(expr):
     sub_expressions = re.findall(_expr_regex, expr)
     if not all([_valid_sub_expr.match(sub_exp) for sub_exp in sub_expressions]):
         return False
+
     return True
 
 


Test patch:
diff --git a/test/python/qobj/test_pulse_converter.py b/test/python/qobj/test_pulse_converter.py
--- a/test/python/qobj/test_pulse_converter.py
+++ b/test/python/qobj/test_pulse_converter.py
@@ -247,17 +247,28 @@ def test_expression_sanitizer(self):
         self.assertFalse(_is_math_expr_safe('INSERT INTO students VALUES (?,?)'))
         self.assertFalse(_is_math_expr_safe('import math'))
         self.assertFalse(_is_math_expr_safe('complex'))
+        self.assertFalse(_is_math_expr_safe('__import__("os").system("clear")'))
+        self.assertFalse(_is_math_expr_safe('eval("()._" + "_class_" + "_._" +'
+                                            ' "_bases_" + "_[0]")'))
         self.assertFalse(_is_math_expr_safe('2***2'))
         self.assertFalse(_is_math_expr_safe('avdfd*3'))
         self.assertFalse(_is_math_expr_safe('Cos(1+2)'))
+        self.assertFalse(_is_math_expr_safe('hello'))
         self.assertFalse(_is_math_expr_safe('hello_world'))
         self.assertFalse(_is_math_expr_safe('1_2'))
         self.assertFalse(_is_math_expr_safe('2+-2'))
+        self.assertFalse(_is_math_expr_safe('print(1.0)'))
+        self.assertFalse(_is_math_expr_safe('1.1.1.1'))
+        self.assertFalse(_is_math_expr_safe('abc.1'))
 
         self.assertTrue(_is_math_expr_safe('1+1*2*3.2+8*cos(1)**2'))
         self.assertTrue(_is_math_expr_safe('pi*2'))
         self.assertTrue(_is_math_expr_safe('-P1*cos(P2)'))
         self.assertTrue(_is_math_expr_safe('-P1*P2*P3'))
+        self.assertTrue(_is_math_expr_safe('-P1'))
+        self.assertTrue(_is_math_expr_safe('-1.*P1'))
+        self.assertTrue(_is_math_expr_safe('-1.*P1*P2'))
+        self.assertTrue(_is_math_expr_safe('-(P1)'))
 
 
 class TestLoConverter(QiskitTestCase):