From b11af6b5ef159c1b3e8da9a69f72e17b920d660a Mon Sep 17 00:00:00 2001 From: Oscar Benjamin Date: Sat, 1 Nov 2025 20:29:25 +0000 Subject: [PATCH] fix(fq_default): store variable name as str Fixes gh-325 Previously, the variable name was stored as a char* pointing to the buffer for a bytes object that might be deallocated. --- src/flint/test/test_all.py | 5 +--- src/flint/types/fq_default.pxd | 2 +- src/flint/types/fq_default.pyx | 46 +++++++++++++++++----------------- 3 files changed, 25 insertions(+), 28 deletions(-) diff --git a/src/flint/test/test_all.py b/src/flint/test/test_all.py index 83092f76..4f21de11 100644 --- a/src/flint/test/test_all.py +++ b/src/flint/test/test_all.py @@ -4720,9 +4720,6 @@ def test_fq_default(): assert raises(lambda: flint.fq_default_ctx(5, fq_type=-1), ValueError) assert raises(lambda: flint.fq_default_ctx("ABC"), TypeError) # type: ignore - # var must be one character - assert raises(lambda: flint.fq_default_ctx(5, var="XXX"), ValueError) - # p must be set if modulus has no characteristic / modulus assert raises(lambda: flint.fq_default_ctx(modulus=[0,1,0]), ValueError) @@ -4777,7 +4774,7 @@ def test_fq_default(): assert str(gf_5) == "Context for fq_default in GF(5)" assert str(gf_5_2) == "Context for fq_default in GF(5^2)[z]/(z^2 + 4*z + 2)" - assert repr(gf_5) == "fq_default_ctx(5, var='z' type='NMOD')" + assert repr(gf_5) == "fq_default_ctx(5, var='z', type='NMOD')" assert repr(gf_5_2) == "fq_default_ctx(5, 2, 'z', x^2 + 4*x + 2, 'FQ_ZECH')" # coercision diff --git a/src/flint/types/fq_default.pxd b/src/flint/types/fq_default.pxd index ddb64ef6..53c2fe64 100644 --- a/src/flint/types/fq_default.pxd +++ b/src/flint/types/fq_default.pxd @@ -24,7 +24,7 @@ cpdef enum fq_default_type: cdef class fq_default_ctx: cdef fq_default_ctx_t val - cdef readonly char *var + cdef str var cdef bint _initialized cdef new_ctype_fq_default(self) diff --git a/src/flint/types/fq_default.pyx b/src/flint/types/fq_default.pyx index 637a86c0..c8478e5e 100644 --- a/src/flint/types/fq_default.pyx +++ b/src/flint/types/fq_default.pyx @@ -63,19 +63,14 @@ cdef class fq_default_ctx: return fq_type @staticmethod - def _parse_input_var(var): + def _parse_input_var(var) -> str: # If no variable is given, use x if var is None: - var = b"z" - - # Encode to bytes for cython to parse - if isinstance(var, str): - var = var.encode() - - # TODO: Flint only wants one-character inputs - if len(var) > 1: - raise ValueError("variable for GF(p^k) generator can only be one character") - + var = "z" + elif isinstance(var, bytes): + var = var.decode() + if len(var) < 1: + raise ValueError("variable for GF(p^k) generator must be at least one character") return var def __init__(self, p=None, degree=None, var=None, modulus=None, fq_type=fq_default_type.DEFAULT, @@ -138,7 +133,7 @@ cdef class fq_default_ctx: if d < 1: raise ValueError(f"the degree must be positive, got d = {d}") - fq_default_ctx_init_type(self.val, (prime).val, d, self.var, fq_type) + fq_default_ctx_init_type(self.val, (prime).val, d, self.var.encode(), fq_type) self._initialized = True cdef _set_from_modulus(self, modulus, var, fq_type=fq_default_type.DEFAULT, @@ -161,7 +156,7 @@ cdef class fq_default_ctx: raise ValueError("modulus must be irreducible") fq_default_ctx_init_modulus_type(self.val, (modulus).val, - (modulus).ctx.mod.val, self.var, fq_type) + (modulus).ctx.mod.val, self.var.encode(), fq_type) self._initialized = True @property @@ -396,15 +391,20 @@ cdef class fq_default_ctx: >>> gf3 == gf False """ + cdef fq_default_ctx other_ctx + if self is other: return True - if typecheck(other, fq_default_ctx): - return (self.fq_type == other.fq_type - and self.var == other.var - and self.prime() == other.prime() - and self.modulus() == other.modulus()) - return False + if not typecheck(other, fq_default_ctx): + return NotImplemented + + other_ctx = other + + return (self.fq_type == other_ctx.fq_type + and self.var == other_ctx.var + and self.prime() == other_ctx.prime() + and self.modulus() == other_ctx.modulus()) def __hash__(self): return hash((self.fq_type, self.var, self.prime(), self.modulus())) @@ -412,12 +412,12 @@ cdef class fq_default_ctx: def __str__(self): if self.degree() == 1: return f"Context for fq_default in GF({self.prime()})" - return f"Context for fq_default in GF({self.prime()}^{self.degree()})[{self.var.decode()}]/({self.modulus().str(var=self.var.decode())})" + return f"Context for fq_default in GF({self.prime()}^{self.degree()})[{self.var}]/({self.modulus().str(var=self.var)})" def __repr__(self): if self.degree() == 1: - return f"fq_default_ctx({self.prime()}, var='{self.var.decode()}' type='{self.fq_type._name_}')" - return f"fq_default_ctx({self.prime()}, {self.degree()}, '{self.var.decode()}', {self.modulus()!r}, '{self.fq_type._name_}')" + return f"fq_default_ctx({self.prime()}, var='{self.var}', type='{self.fq_type._name_}')" + return f"fq_default_ctx({self.prime()}, {self.degree()}, '{self.var}', {self.modulus()!r}, '{self.fq_type._name_}')" def __call__(self, val): return fq_default(val, self) @@ -503,7 +503,7 @@ cdef class fq_default(flint_scalar): return coeffs def str(self): - return self.polynomial().str(var=self.ctx.var.decode()) + return self.polynomial().str(var=self.ctx.var) def __hash__(self): return hash((self.polynomial(), hash(self.ctx)))