Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package gap-json for openSUSE:Factory checked in at 2026-09-28 10:44:31 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/gap-json (Old) and /work/SRC/openSUSE:Factory/.gap-json.new.383539 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "gap-json" Mon Sep 28 10:44:31 2026 rev:8 rq:1380731 version:2.5.0 Changes: -------- --- /work/SRC/openSUSE:Factory/gap-json/gap-json.changes 2026-05-10 16:48:47.257916934 +0200 +++ /work/SRC/openSUSE:Factory/.gap-json.new.383539/gap-json.changes 2026-09-28 10:44:43.509610083 +0200 @@ -1,0 +2,7 @@ +Fri Sep 25 21:45:02 UTC 2026 - Jan Engelhardt <[email protected]> + +- Update to release 2.5.0 + * Avoid format-string vulnerabilities + * Fix buffer over-read in getUTF8Char + +------------------------------------------------------------------- Old: ---- json-2.4.0.tar.gz New: ---- json-2.5.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ gap-json.spec ++++++ --- /var/tmp/diff_new_pack.VUczEn/_old 2026-09-28 10:44:45.359687595 +0200 +++ /var/tmp/diff_new_pack.VUczEn/_new 2026-09-28 10:44:45.363687762 +0200 @@ -17,7 +17,7 @@ Name: gap-json -Version: 2.4.0 +Version: 2.5.0 Release: 0 Summary: GAP: Package for reading and writing JSON License: BSD-2-Clause @@ -26,10 +26,9 @@ #Git-Clone: https://github.com/gap-packages/json Source: https://github.com/gap-packages/json/releases/download/v%version/json-%version.tar.gz BuildRequires: c++_compiler -BuildRequires: gap-devel >= 4.12 +BuildRequires: gap-devel >= 4.15 BuildRequires: gap-rpm-devel -Requires: gap-core >= 4.12 -Requires: gap-gapdoc >= 1.5 +Requires: gap-core >= 4.15 %description Enhances GAP by the ability to read and write JSON files. ++++++ _scmsync.obsinfo ++++++ --- /var/tmp/diff_new_pack.VUczEn/_old 2026-09-28 10:44:45.480692664 +0200 +++ /var/tmp/diff_new_pack.VUczEn/_new 2026-09-28 10:44:45.489693041 +0200 @@ -1,5 +1,5 @@ -mtime: 1778391754 -commit: 22f29de56f045210cd72e3667bd25c1260f2dfd67d845af1d2a2e0f3e68ca30f +mtime: 1790372720 +commit: d81a076889674b272124c1bd5011e1f6080787cd9f0bf9c7fafa0fd39d682d46 url: https://src.opensuse.org/jengelh/gap-json revision: master ++++++ build.specials.obscpio ++++++ ++++++ build.specials.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/.gitignore new/.gitignore --- old/.gitignore 1970-01-01 01:00:00.000000000 +0100 +++ new/.gitignore 2026-09-25 23:45:20.000000000 +0200 @@ -0,0 +1 @@ +.osc ++++++ json-2.4.0.tar.gz -> json-2.5.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/json-2.4.0/PackageInfo.g new/json-2.5.0/PackageInfo.g --- old/json-2.4.0/PackageInfo.g 2026-05-08 02:00:00.000000000 +0200 +++ new/json-2.5.0/PackageInfo.g 2026-09-25 02:00:00.000000000 +0200 @@ -10,8 +10,8 @@ PackageName := "json", Subtitle := "Reading and Writing JSON", -Version := "2.4.0", -Date := "08/05/2026", # dd/mm/yyyy format +Version := "2.5.0", +Date := "25/09/2026", # dd/mm/yyyy format License := "BSD-2-Clause", Persons := [ @@ -69,8 +69,8 @@ ), Dependencies := rec( - GAP := ">= 4.12", - NeededOtherPackages := [ [ "GAPDoc", ">= 1.5" ] ], + GAP := ">= 4.15", + NeededOtherPackages := [ ], SuggestedOtherPackages := [ ], ExternalConditions := [ ], ), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/json-2.4.0/README new/json-2.5.0/README --- old/json-2.4.0/README 2026-05-08 02:00:00.000000000 +0200 +++ new/json-2.5.0/README 2026-09-25 02:00:00.000000000 +0200 @@ -19,7 +19,25 @@ Updates ======= -v2.3.0 +v2.5.0 +====== + +- Speed up GapToJsonString by serialising common GAP objects directly in the + kernel. + +- Fix a buffer over-read when escaping a string whose last character is a + truncated multi-byte UTF-8 sequence, such as [ CHAR_INT(200) ]. Reading past + the end of the string now yields 0 rather than whatever happened to follow + it, so the sequence falls back to Latin-1 like any other malformed one + instead of raising an error or reading out of bounds. + +- Avoid format-string vulnerabilities when reporting invalid JSON. + +- Remove the unnecessary run-time dependency on GAPDoc. + +- Require GAP 4.15 or later. + +v2.4.0 ====== Allow outputting 'fail', it maps to null (null was already read in as fail) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/json-2.4.0/doc/_Chapter_GAP-JSON_mapping.xml new/json-2.5.0/doc/_Chapter_GAP-JSON_mapping.xml --- old/json-2.4.0/doc/_Chapter_GAP-JSON_mapping.xml 2026-05-08 02:00:00.000000000 +0200 +++ new/json-2.5.0/doc/_Chapter_GAP-JSON_mapping.xml 2026-09-25 02:00:00.000000000 +0200 @@ -41,7 +41,6 @@ </Description> </ManSection> - <ManSection> <Func Arg="value" Name="GapToJsonString" /> <Returns>string @@ -51,7 +50,6 @@ </Description> </ManSection> - <ManSection> <Func Arg="string" Name="JsonStringToGap" /> <Returns>value @@ -61,7 +59,6 @@ </Description> </ManSection> - <ManSection> <Func Arg="stream" Name="JsonStreamToGap" /> <Returns>value @@ -71,9 +68,7 @@ </Description> </ManSection> - </Section> - </Chapter> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/json-2.4.0/doc/_entities.xml new/json-2.5.0/doc/_entities.xml --- old/json-2.4.0/doc/_entities.xml 2026-05-08 02:00:00.000000000 +0200 +++ new/json-2.5.0/doc/_entities.xml 2026-09-25 02:00:00.000000000 +0200 @@ -1 +1,4 @@ +<!ENTITY RELEASEDATE '25 September 2026'> +<!ENTITY RELEASEYEAR '2026'> +<!ENTITY VERSION '2.5.0'> <!ENTITY json '<Package>json</Package>'> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/json-2.4.0/doc/_main.tex new/json-2.5.0/doc/_main.tex --- old/json-2.4.0/doc/_main.tex 1970-01-01 01:00:00.000000000 +0100 +++ new/json-2.5.0/doc/_main.tex 2026-09-25 02:00:00.000000000 +0200 @@ -0,0 +1,234 @@ +% generated by GAPDoc2LaTeX from XML source (Frank Luebeck) +\documentclass[a4paper,11pt]{report} + +\usepackage[top=37mm,bottom=37mm,left=27mm,right=27mm]{geometry} +\sloppy +\pagestyle{myheadings} +\usepackage{amssymb} +\usepackage[utf8]{inputenc} +\usepackage{makeidx} +\makeindex +\usepackage{color} +\definecolor{FireBrick}{rgb}{0.5812,0.0074,0.0083} +\definecolor{RoyalBlue}{rgb}{0.0236,0.0894,0.6179} +\definecolor{RoyalGreen}{rgb}{0.0236,0.6179,0.0894} +\definecolor{RoyalRed}{rgb}{0.6179,0.0236,0.0894} +\definecolor{LightBlue}{rgb}{0.8544,0.9511,1.0000} +\definecolor{Black}{rgb}{0.0,0.0,0.0} + +\definecolor{linkColor}{rgb}{0.0,0.0,0.554} +\definecolor{citeColor}{rgb}{0.0,0.0,0.554} +\definecolor{fileColor}{rgb}{0.0,0.0,0.554} +\definecolor{urlColor}{rgb}{0.0,0.0,0.554} +\definecolor{promptColor}{rgb}{0.0,0.0,0.589} +\definecolor{brkpromptColor}{rgb}{0.589,0.0,0.0} +\definecolor{gapinputColor}{rgb}{0.589,0.0,0.0} +\definecolor{gapoutputColor}{rgb}{0.0,0.0,0.0} + +%% for a long time these were red and blue by default, +%% now black, but keep variables to overwrite +\definecolor{FuncColor}{rgb}{0.0,0.0,0.0} +%% strange name because of pdflatex bug: +\definecolor{Chapter }{rgb}{0.0,0.0,0.0} +\definecolor{DarkOlive}{rgb}{0.1047,0.2412,0.0064} + + +\usepackage{fancyvrb} + +\usepackage{mathptmx,helvet} +\usepackage[T1]{fontenc} +\usepackage{textcomp} + + +\usepackage[ + pdftex=true, + bookmarks=true, + a4paper=true, + pdftitle={Written with GAPDoc}, + pdfcreator={LaTeX with hyperref package / GAPDoc}, + colorlinks=true, + backref=page, + breaklinks=true, + linkcolor=linkColor, + citecolor=citeColor, + filecolor=fileColor, + urlcolor=urlColor, + pdfpagemode={UseNone}, + ]{hyperref} + +\newcommand{\maintitlesize}{\fontsize{50}{55}\selectfont} + +% write page numbers to a .pnr log file for online help +\newwrite\pagenrlog +\immediate\openout\pagenrlog =\jobname.pnr +\immediate\write\pagenrlog{PAGENRS := [} +\newcommand{\logpage}[1]{\protect\write\pagenrlog{#1, \thepage,}} +%% were never documented, give conflicts with some additional packages + +\newcommand{\GAP}{\textsf{GAP}} + +%% nicer description environments, allows long labels +\usepackage{enumitem} +\setdescription{style=nextline} + +%% depth of toc +\setcounter{tocdepth}{1} + + + + + +%% command for ColorPrompt style examples +\newcommand{\gapprompt}[1]{\color{promptColor}{\bfseries #1}} +\newcommand{\gapbrkprompt}[1]{\color{brkpromptColor}{\bfseries #1}} +\newcommand{\gapinput}[1]{\color{gapinputColor}{#1}} + + +\begin{document} + +\logpage{[ 0, 0, 0 ]} +\begin{titlepage} +\mbox{}\vfill + +\begin{center}{\maintitlesize \textbf{ json \\ +\mbox{}}}\\ +\vfill + +\hypersetup{pdftitle={ json }} +\markright{\scriptsize \mbox{}\hfill json \hfill\mbox{}} +{\Huge \textbf{ Reading and Writing JSON \\ +\mbox{}}}\\ +\vfill + +{\Huge 2.5.0 \mbox{}}\\[1cm] +{ 25 September 2026 \mbox{}}\\[1cm] +\mbox{}\\[2cm] +{\Large \textbf{\strut Christopher Jefferson \strut\mbox{}}}\\ +\hypersetup{pdfauthor={ Christopher Jefferson }} +\end{center}\vfill + +\mbox{}\\ +{\mbox{}\\ +\small \noindent \textbf{ Christopher Jefferson } Email: \href{mailto://[email protected]} {\texttt{caj21@st\texttt{\symbol{45}}andrews.ac.uk}}\\ + Homepage: \href{https://heather.cafe/} {\texttt{https://heather.cafe/}}\\ + Address: \begin{minipage}[t]{8cm}\noindent + St Andrews\\ + Scotland\\ + UK\\ + \end{minipage} +}\\ +\end{titlepage} + +\newpage\setcounter{page}{2} +\newpage + +\def\contentsname{Contents\logpage{[ 0, 0, 1 ]}} + +\tableofcontents +\newpage + + +\chapter{\textcolor{Chapter }{GAP\texttt{\symbol{45}}JSON mapping}}\label{Chapter_GAP-JSON_mapping} +\logpage{[ 1, 0, 0 ]} +\hyperdef{L}{X87B639367C9770AE}{} +{ + This package defines a mapping between the JSON markup language and GAP. The +built\texttt{\symbol{45}}in datatypes of GAP provide an easy mapping to and +from JSON. This package uses the following mapping between GAP and JSON. + + +\begin{itemize} +\item JSON lists are mapped to GAP lists +\item JSON dictionaries are mapped to GAP records +\item JSON strings are mapped to GAP strings +\item Integers are mapped to GAP integers, non\texttt{\symbol{45}}integer numbers +are mapped to Floats +\item true, false and null are mapped to true, false and fail respectively +\end{itemize} + + + Note that this library is *NOT* intended to provide a general purpose library +for transmitting any GAP object. If you wish to do this, look at the openmath +package, or IO{\textunderscore}Pickle in the IO package. +\section{\textcolor{Chapter }{Methods}}\label{Chapter_GAP-JSON_mapping_Section_Methods} +\logpage{[ 1, 1, 0 ]} +\hyperdef{L}{X8606FDCE878850EF}{} +{ + + +\subsection{\textcolor{Chapter }{GapToJsonStream}} +\logpage{[ 1, 1, 1 ]}\nobreak +\hyperdef{L}{X83B041337FB4E4D1}{} +{\noindent\textcolor{FuncColor}{$\triangleright$\enspace\texttt{GapToJsonStream({\mdseries\slshape stream, value})\index{GapToJsonStream@\texttt{GapToJsonStream}} +\label{GapToJsonStream} +}\hfill{\scriptsize (function)}}\\ + + + Converts the \mbox{\texttt{\mdseries\slshape value}} to JSON, and outputs it to \mbox{\texttt{\mdseries\slshape stream}}. This function disables GAP's usual line splitting while JSON is being +outputted. } + + + +\subsection{\textcolor{Chapter }{GapToJsonString}} +\logpage{[ 1, 1, 2 ]}\nobreak +\hyperdef{L}{X855531F88506F3D0}{} +{\noindent\textcolor{FuncColor}{$\triangleright$\enspace\texttt{GapToJsonString({\mdseries\slshape value})\index{GapToJsonString@\texttt{GapToJsonString}} +\label{GapToJsonString} +}\hfill{\scriptsize (function)}}\\ +\textbf{\indent Returns:\ } +string + + + + Converts a GAP \mbox{\texttt{\mdseries\slshape value}} to a JSON string. } + + + +\subsection{\textcolor{Chapter }{JsonStringToGap}} +\logpage{[ 1, 1, 3 ]}\nobreak +\hyperdef{L}{X823A762684DC39C1}{} +{\noindent\textcolor{FuncColor}{$\triangleright$\enspace\texttt{JsonStringToGap({\mdseries\slshape string})\index{JsonStringToGap@\texttt{JsonStringToGap}} +\label{JsonStringToGap} +}\hfill{\scriptsize (function)}}\\ +\textbf{\indent Returns:\ } +value + + + + Converts a JSON \mbox{\texttt{\mdseries\slshape string}} into a GAP value. } + + + +\subsection{\textcolor{Chapter }{JsonStreamToGap}} +\logpage{[ 1, 1, 4 ]}\nobreak +\hyperdef{L}{X823DB24A7D2F7D64}{} +{\noindent\textcolor{FuncColor}{$\triangleright$\enspace\texttt{JsonStreamToGap({\mdseries\slshape stream})\index{JsonStreamToGap@\texttt{JsonStreamToGap}} +\label{JsonStreamToGap} +}\hfill{\scriptsize (function)}}\\ +\textbf{\indent Returns:\ } +value + + + + Reads a single JSON object from a \mbox{\texttt{\mdseries\slshape stream}} and converts it to a GAP value. } + + } + + } + + \def\indexname{Index\logpage{[ "Ind", 0, 0 ]} +\hyperdef{L}{X83A0356F839C696F}{} +} + +\cleardoublepage +\phantomsection +\addcontentsline{toc}{chapter}{Index} + + +\printindex + +\immediate\write\pagenrlog{["Ind", 0, 0], \arabic{page},} +\newpage +\immediate\write\pagenrlog{["End"], \arabic{page}];} +\immediate\closeout\pagenrlog +\end{document} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/json-2.4.0/doc/_main.xml new/json-2.5.0/doc/_main.xml --- old/json-2.4.0/doc/_main.xml 1970-01-01 01:00:00.000000000 +0100 +++ new/json-2.5.0/doc/_main.xml 2026-09-25 02:00:00.000000000 +0200 @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!-- This is an automatically generated file. --> +<!DOCTYPE Book SYSTEM "gapdoc.dtd" +[ + <#Include SYSTEM "_entities.xml"> +] +> +<Book Name="json"> +<#Include SYSTEM "title.xml"> +<TableOfContents/> +<Body> +<#Include SYSTEM "_AutoDocMainFile.xml"> +</Body> +<TheIndex/> +</Book> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/json-2.4.0/doc/chap0.html new/json-2.5.0/doc/chap0.html --- old/json-2.4.0/doc/chap0.html 2026-05-08 02:00:00.000000000 +0200 +++ new/json-2.5.0/doc/chap0.html 2026-09-25 02:00:00.000000000 +0200 @@ -29,10 +29,10 @@ <h2>Reading and Writing JSON</h2> <p> - 2.4.0</p> + 2.5.0</p> <p> - 8 May 2026 + 25 September 2026 </p> </div> @@ -41,7 +41,6 @@ - </b> <br />Email: <span class="URL"><a href="mailto:[email protected]">[email protected]</a></span> <br />Homepage: <span class="URL"><a href="https://heather.cafe/">https://heather.cafe/</a></span> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/json-2.4.0/doc/chap0.txt new/json-2.5.0/doc/chap0.txt --- old/json-2.4.0/doc/chap0.txt 2026-05-08 02:00:00.000000000 +0200 +++ new/json-2.5.0/doc/chap0.txt 2026-09-25 02:00:00.000000000 +0200 @@ -6,10 +6,10 @@ [1X Reading and Writing JSON [101X - 2.4.0 + 2.5.0 - 8 May 2026 + 25 September 2026 Christopher Jefferson diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/json-2.4.0/doc/chap0_mj.html new/json-2.5.0/doc/chap0_mj.html --- old/json-2.4.0/doc/chap0_mj.html 2026-05-08 02:00:00.000000000 +0200 +++ new/json-2.5.0/doc/chap0_mj.html 2026-09-25 02:00:00.000000000 +0200 @@ -32,10 +32,10 @@ <h2>Reading and Writing JSON</h2> <p> - 2.4.0</p> + 2.5.0</p> <p> - 8 May 2026 + 25 September 2026 </p> </div> @@ -44,7 +44,6 @@ - </b> <br />Email: <span class="URL"><a href="mailto:[email protected]">[email protected]</a></span> <br />Homepage: <span class="URL"><a href="https://heather.cafe/">https://heather.cafe/</a></span> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/json-2.4.0/doc/chooser.html new/json-2.5.0/doc/chooser.html --- old/json-2.4.0/doc/chooser.html 2026-05-08 02:00:00.000000000 +0200 +++ new/json-2.5.0/doc/chooser.html 2026-09-25 02:00:00.000000000 +0200 @@ -23,9 +23,18 @@ return ""; } +<!-- load dark appearance either explicitly or via the OS preference --> +function writeAppearanceStyle(mode) { + if (mode == "dark") { + document.writeln('<link rel="stylesheet" type="text/css" href="dark.css" />'); + } else if (mode != "light") { + document.writeln('<style type="text/css">@import "dark.css" (prefers-color-scheme: dark);</style>'); + } +} + <!-- find style from current values in form --> function getstyle() { - var choices = ["toggle","colorprompt","tocside","font","justify"]; + var choices = ["toggle","colorprompt","tocside","font","justify","appearance"]; var style = ""; for (var i=0; choices.length > i; i++) { var a = currval(choices[i]); @@ -55,17 +64,23 @@ <!-- initialize form from GAPDocStyle cookie --> function initform() { var style = valueString(document.cookie, "GAPDocStyle"); + if (style == 0 || style == "default") + writeAppearanceStyle(""); if (style != 0 && style.length > 0 && style.length != "default") { stlist = style.split(","); var chform = document.forms[0].elements; + var appearance = ""; for (var i=0; chform.length > i; i++) { if (chform[i].type == "radio") { for (var j=0; stlist.length > j; j++) { - if (chform[i].value == stlist[j]) + if (stlist[j] == "dark" || stlist[j] == "light") + appearance = stlist[j]; + if (chform[i].value == stlist[j]) chform[i].checked = true; } } } + writeAppearanceStyle(appearance); } } @@ -119,6 +134,15 @@ <input type="radio" name="justify" value="ragged" onclick="javascript:f()"/> ragged right </p> +<p> +Appearance: +<input type="radio" name="appearance" value="" +onclick="javascript:f()"/> system (default) +<input type="radio" name="appearance" value="light" checked="checked" +onclick="javascript:f()"/> light +<input type="radio" name="appearance" value="dark" +onclick="javascript:f()"/> dark +</p> </form> <p> <a name="backLINK" href=""><strong>Apply settings to last page.</strong></a> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/json-2.4.0/doc/dark.css new/json-2.5.0/doc/dark.css --- old/json-2.4.0/doc/dark.css 1970-01-01 01:00:00.000000000 +0100 +++ new/json-2.5.0/doc/dark.css 2026-09-25 02:00:00.000000000 +0200 @@ -0,0 +1,178 @@ +/* dark.css Frank Luebeck */ +/* Initial dark theme contributed by kiryph in issue #75. */ + +/* colors */ +body { + background: #121212; + color: #eee; +} + +a:link { + color: #576cad; +} + +a:visited { + color: #576cad; +} + +a:active { + color: #eee; +} + +a:hover { + background: #eee; +} + +pre { + color: black; +} + +tt, code { + color: #eee; +} + +/* layout for the definitions of functions, variables, ... */ +div.func { + background: #909090; +} + +/* Example elements (for old converted manuals, now in div+pre */ +table.example { + background: #efefef; +} + +/* becomes ... */ +div.example { + background: #efefef; + color: black; +} + +/* Links to chapters in all files at top and bottom. */ +div.chlinktop { + background: #22272e; + border-color: #3a414a; + color: #d7dde5; +} + +div.chlinktop a:hover { + background: #2f3742; +} + +div.chlinkbot { + background: #22272e; + border-color: #3a414a; + color: #d7dde5; +} + +/* and this is for the "Top", "Prev", "Next" links */ +div.chlinkprevnexttop { + background: #22272e; + border-color: #3a414a; + color: #d7dde5; +} + +div.chlinkprevnexttop a:hover { + background: #2f3742; +} + +div.chlinkprevnextbot { + background: #22272e; + border-color: #3a414a; + color: #d7dde5; +} + +div.chlinkprevnextbot a:hover { + background: #2f3742; +} + +div.ContChap div.ContSect:hover div.ContSSBlock { + background: #eee; + border-color: #666; + color: #000; +} + +div.ContSSBlock a:hover { + background: #fff; +} + +/* and here for the side menu of contents in the chapter files */ +div.ChapSects a:hover { + background: #eee; + color: #000; +} + +div.ChapSects div.ContSect:hover div.ContSSBlock { + background: #b5b5b5; + border-color: #666; + color: #000; +} + +div.ChapSects div.ContSect:hover div.ContSSBlock a:hover { + background: #828282; +} + +/* Table elements */ +table.GAPDocTable { + border-color: black; +} + +table.GAPDocTable td, table.GAPDocTable th { + border-color: #555; +} + +table.GAPDocTablenoborder td, table.GAPDocTable th { + border-color: #555; +} + +/* Colors and fonts can be overwritten for some types of elements. */ +/* Verb elements */ +pre.normal { + color: #eee; +} + +/* Func-like elements and Ref to Func-like */ +code.func { + color: #eee; +} + +/* K elements */ +code.keyw { + color: #983d3d; +} + +/* F elements */ +code.file { + color: #8e4510; +} + +/* Arg elements */ +var.Arg { + color: #060; +} + +/* colors for ColorPrompt like examples */ +span.GAPprompt { + color: #000097; +} + +span.GAPbrkprompt { + color: #970000; +} + +span.GAPinput { + color: #970000; +} + +/* Bib entries */ +span.BibKey { + color: #052; +} + +/* for light and dark mode pictures */ +.only-on-dark { + display: block; +} + +.only-on-light { + display: none; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/json-2.4.0/doc/json.tex new/json-2.5.0/doc/json.tex --- old/json-2.4.0/doc/json.tex 2026-05-08 02:00:00.000000000 +0200 +++ new/json-2.5.0/doc/json.tex 1970-01-01 01:00:00.000000000 +0100 @@ -1,235 +0,0 @@ -% generated by GAPDoc2LaTeX from XML source (Frank Luebeck) -\documentclass[a4paper,11pt]{report} - -\usepackage[top=37mm,bottom=37mm,left=27mm,right=27mm]{geometry} -\sloppy -\pagestyle{myheadings} -\usepackage{amssymb} -\usepackage[utf8]{inputenc} -\usepackage{makeidx} -\makeindex -\usepackage{color} -\definecolor{FireBrick}{rgb}{0.5812,0.0074,0.0083} -\definecolor{RoyalBlue}{rgb}{0.0236,0.0894,0.6179} -\definecolor{RoyalGreen}{rgb}{0.0236,0.6179,0.0894} -\definecolor{RoyalRed}{rgb}{0.6179,0.0236,0.0894} -\definecolor{LightBlue}{rgb}{0.8544,0.9511,1.0000} -\definecolor{Black}{rgb}{0.0,0.0,0.0} - -\definecolor{linkColor}{rgb}{0.0,0.0,0.554} -\definecolor{citeColor}{rgb}{0.0,0.0,0.554} -\definecolor{fileColor}{rgb}{0.0,0.0,0.554} -\definecolor{urlColor}{rgb}{0.0,0.0,0.554} -\definecolor{promptColor}{rgb}{0.0,0.0,0.589} -\definecolor{brkpromptColor}{rgb}{0.589,0.0,0.0} -\definecolor{gapinputColor}{rgb}{0.589,0.0,0.0} -\definecolor{gapoutputColor}{rgb}{0.0,0.0,0.0} - -%% for a long time these were red and blue by default, -%% now black, but keep variables to overwrite -\definecolor{FuncColor}{rgb}{0.0,0.0,0.0} -%% strange name because of pdflatex bug: -\definecolor{Chapter }{rgb}{0.0,0.0,0.0} -\definecolor{DarkOlive}{rgb}{0.1047,0.2412,0.0064} - - -\usepackage{fancyvrb} - -\usepackage{mathptmx,helvet} -\usepackage[T1]{fontenc} -\usepackage{textcomp} - - -\usepackage[ - pdftex=true, - bookmarks=true, - a4paper=true, - pdftitle={Written with GAPDoc}, - pdfcreator={LaTeX with hyperref package / GAPDoc}, - colorlinks=true, - backref=page, - breaklinks=true, - linkcolor=linkColor, - citecolor=citeColor, - filecolor=fileColor, - urlcolor=urlColor, - pdfpagemode={UseNone}, - ]{hyperref} - -\newcommand{\maintitlesize}{\fontsize{50}{55}\selectfont} - -% write page numbers to a .pnr log file for online help -\newwrite\pagenrlog -\immediate\openout\pagenrlog =\jobname.pnr -\immediate\write\pagenrlog{PAGENRS := [} -\newcommand{\logpage}[1]{\protect\write\pagenrlog{#1, \thepage,}} -%% were never documented, give conflicts with some additional packages - -\newcommand{\GAP}{\textsf{GAP}} - -%% nicer description environments, allows long labels -\usepackage{enumitem} -\setdescription{style=nextline} - -%% depth of toc -\setcounter{tocdepth}{1} - - - - - -%% command for ColorPrompt style examples -\newcommand{\gapprompt}[1]{\color{promptColor}{\bfseries #1}} -\newcommand{\gapbrkprompt}[1]{\color{brkpromptColor}{\bfseries #1}} -\newcommand{\gapinput}[1]{\color{gapinputColor}{#1}} - - -\begin{document} - -\logpage{[ 0, 0, 0 ]} -\begin{titlepage} -\mbox{}\vfill - -\begin{center}{\maintitlesize \textbf{ json \mbox{}}}\\ -\vfill - -\hypersetup{pdftitle= json } -\markright{\scriptsize \mbox{}\hfill json \hfill\mbox{}} -{\Huge \textbf{ Reading and Writing JSON \mbox{}}}\\ -\vfill - -{\Huge 2.4.0 \mbox{}}\\[1cm] -{ 8 May 2026 \mbox{}}\\[1cm] -\mbox{}\\[2cm] -{\Large \textbf{ Christopher Jefferson\\ - \mbox{}}}\\ -\hypersetup{pdfauthor= Christopher Jefferson\\ - } -\end{center}\vfill - -\mbox{}\\ -{\mbox{}\\ -\small \noindent \textbf{ Christopher Jefferson\\ - } Email: \href{mailto://[email protected]} {\texttt{caj21@st\texttt{\symbol{45}}andrews.ac.uk}}\\ - Homepage: \href{https://heather.cafe/} {\texttt{https://heather.cafe/}}\\ - Address: \begin{minipage}[t]{8cm}\noindent - St Andrews\\ - Scotland\\ - UK\\ - \end{minipage} -}\\ -\end{titlepage} - -\newpage\setcounter{page}{2} -\newpage - -\def\contentsname{Contents\logpage{[ 0, 0, 1 ]}} - -\tableofcontents -\newpage - - -\chapter{\textcolor{Chapter }{GAP\texttt{\symbol{45}}JSON mapping}}\label{Chapter_GAP-JSON_mapping} -\logpage{[ 1, 0, 0 ]} -\hyperdef{L}{X87B639367C9770AE}{} -{ - This package defines a mapping between the JSON markup language and GAP. The -built\texttt{\symbol{45}}in datatypes of GAP provide an easy mapping to and -from JSON. This package uses the following mapping between GAP and JSON. - - -\begin{itemize} -\item JSON lists are mapped to GAP lists -\item JSON dictionaries are mapped to GAP records -\item JSON strings are mapped to GAP strings -\item Integers are mapped to GAP integers, non\texttt{\symbol{45}}integer numbers -are mapped to Floats -\item true, false and null are mapped to true, false and fail respectively -\end{itemize} - - - Note that this library is *NOT* intended to provide a general purpose library -for transmitting any GAP object. If you wish to do this, look at the openmath -package, or IO{\textunderscore}Pickle in the IO package. -\section{\textcolor{Chapter }{Methods}}\label{Chapter_GAP-JSON_mapping_Section_Methods} -\logpage{[ 1, 1, 0 ]} -\hyperdef{L}{X8606FDCE878850EF}{} -{ - - -\subsection{\textcolor{Chapter }{GapToJsonStream}} -\logpage{[ 1, 1, 1 ]}\nobreak -\hyperdef{L}{X83B041337FB4E4D1}{} -{\noindent\textcolor{FuncColor}{$\triangleright$\enspace\texttt{GapToJsonStream({\mdseries\slshape stream, value})\index{GapToJsonStream@\texttt{GapToJsonStream}} -\label{GapToJsonStream} -}\hfill{\scriptsize (function)}}\\ - - - Converts the \mbox{\texttt{\mdseries\slshape value}} to JSON, and outputs it to \mbox{\texttt{\mdseries\slshape stream}}. This function disables GAP's usual line splitting while JSON is being -outputted. } - - - -\subsection{\textcolor{Chapter }{GapToJsonString}} -\logpage{[ 1, 1, 2 ]}\nobreak -\hyperdef{L}{X855531F88506F3D0}{} -{\noindent\textcolor{FuncColor}{$\triangleright$\enspace\texttt{GapToJsonString({\mdseries\slshape value})\index{GapToJsonString@\texttt{GapToJsonString}} -\label{GapToJsonString} -}\hfill{\scriptsize (function)}}\\ -\textbf{\indent Returns:\ } -string - - - - Converts a GAP \mbox{\texttt{\mdseries\slshape value}} to a JSON string. } - - - -\subsection{\textcolor{Chapter }{JsonStringToGap}} -\logpage{[ 1, 1, 3 ]}\nobreak -\hyperdef{L}{X823A762684DC39C1}{} -{\noindent\textcolor{FuncColor}{$\triangleright$\enspace\texttt{JsonStringToGap({\mdseries\slshape string})\index{JsonStringToGap@\texttt{JsonStringToGap}} -\label{JsonStringToGap} -}\hfill{\scriptsize (function)}}\\ -\textbf{\indent Returns:\ } -value - - - - Converts a JSON \mbox{\texttt{\mdseries\slshape string}} into a GAP value. } - - - -\subsection{\textcolor{Chapter }{JsonStreamToGap}} -\logpage{[ 1, 1, 4 ]}\nobreak -\hyperdef{L}{X823DB24A7D2F7D64}{} -{\noindent\textcolor{FuncColor}{$\triangleright$\enspace\texttt{JsonStreamToGap({\mdseries\slshape stream})\index{JsonStreamToGap@\texttt{JsonStreamToGap}} -\label{JsonStreamToGap} -}\hfill{\scriptsize (function)}}\\ -\textbf{\indent Returns:\ } -value - - - - Reads a single JSON object from a \mbox{\texttt{\mdseries\slshape stream}} and converts it to a GAP value. } - - } - - } - - \def\indexname{Index\logpage{[ "Ind", 0, 0 ]} -\hyperdef{L}{X83A0356F839C696F}{} -} - -\cleardoublepage -\phantomsection -\addcontentsline{toc}{chapter}{Index} - - -\printindex - -\immediate\write\pagenrlog{["Ind", 0, 0], \arabic{page},} -\newpage -\immediate\write\pagenrlog{["End"], \arabic{page}];} -\immediate\closeout\pagenrlog -\end{document} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/json-2.4.0/doc/json.xml new/json-2.5.0/doc/json.xml --- old/json-2.4.0/doc/json.xml 2026-05-08 02:00:00.000000000 +0200 +++ new/json-2.5.0/doc/json.xml 1970-01-01 01:00:00.000000000 +0100 @@ -1,16 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> - -<!-- This is an automatically generated file. --> -<!DOCTYPE Book SYSTEM "gapdoc.dtd" -[ - [<#Include SYSTEM "_entities.xml"> -] -> -<Book Name="json"> -<#Include SYSTEM "title.xml"> -<TableOfContents/> -<Body> -<#Include SYSTEM "_AutoDocMainFile.xml"> -</Body> -<TheIndex/> -</Book> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/json-2.4.0/doc/manual.css new/json-2.5.0/doc/manual.css --- old/json-2.4.0/doc/manual.css 2026-05-08 02:00:00.000000000 +0200 +++ new/json-2.5.0/doc/manual.css 2026-09-25 02:00:00.000000000 +0200 @@ -477,6 +477,10 @@ span.BibHowpublished { } +/* for light and dark mode pictures */ +.only-on-dark { + display: none; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/json-2.4.0/doc/manual.js new/json-2.5.0/doc/manual.js --- old/json-2.4.0/doc/manual.js 2026-05-08 02:00:00.000000000 +0200 +++ new/json-2.5.0/doc/manual.js 2026-09-25 02:00:00.000000000 +0200 @@ -47,6 +47,20 @@ return 0; } +/* load dark appearance either explicitly or via the OS preference */ +function writeAppearanceStyle(mode) { + if (mode == "dark") { + document.writeln( + '<link rel="stylesheet" type="text/css" href="dark.css" />' + ); + } else if (mode != "light") { + document.writeln( + '<style type="text/css">@import "dark.css" ' + + '(prefers-color-scheme: dark);</style>' + ); + } +} + /* when a non-default style is chosen via URL or a cookie, then the cookie is reset and the styles .js and .css files are read */ function overwriteStyle() { @@ -55,6 +69,8 @@ /* otherwise check cookie */ if (style == 0) style = valueString(document.cookie, "GAPDocStyle"); + if (style == 0 || style == "default") + writeAppearanceStyle(""); if (style == 0) return; if (style == "default") @@ -70,13 +86,19 @@ document.cookie = "GAPDocStyle="+style+";Path="+path; /* split into names of style files */ var stlist = style.split(","); + var appearance = ""; /* read style's css and js files */ for (var i=0; i < stlist.length; i++) { - document.writeln('<link rel="stylesheet" type="text/css" href="'+ - stlist[i]+'.css" />'); - document.writeln('<script src="'+stlist[i]+ - '.js" type="text/javascript"></script>'); + if (stlist[i] == "dark" || stlist[i] == "light") { + appearance = stlist[i]; + } else { + document.writeln('<link rel="stylesheet" type="text/css" href="'+ + stlist[i]+'.css" />'); + document.writeln('<script src="'+stlist[i]+ + '.js" type="text/javascript"></script>'); + } } + writeAppearanceStyle(appearance); } } @@ -110,4 +132,3 @@ for (var i=0; i < jscontentfuncs.length; i++) jscontentfuncs[i](); } - Binary files old/json-2.4.0/doc/manual.pdf and new/json-2.5.0/doc/manual.pdf differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/json-2.4.0/doc/title.xml new/json-2.5.0/doc/title.xml --- old/json-2.4.0/doc/title.xml 2026-05-08 02:00:00.000000000 +0200 +++ new/json-2.5.0/doc/title.xml 2026-09-25 02:00:00.000000000 +0200 @@ -9,10 +9,10 @@ Reading and Writing JSON </Subtitle> <Version> - 2.4.0 + 2.5.0 </Version> <Author> - Christopher Jefferson<Alt Only="LaTeX"><Br/></Alt> + Christopher Jefferson <Address> St Andrews<Br/> Scotland<Br/> @@ -20,9 +20,8 @@ </Address> <Email>[email protected]</Email> <Homepage>https://heather.cafe/</Homepage> - </Author> <Date> - 8 May 2026 + 25 September 2026 </Date> </TitlePage> \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/json-2.4.0/gap/json.gi new/json-2.5.0/gap/json.gi --- old/json-2.4.0/gap/json.gi 2026-05-08 02:00:00.000000000 +0200 +++ new/json-2.5.0/gap/json.gi 2026-09-25 02:00:00.000000000 +0200 @@ -25,6 +25,18 @@ _JSON_Globals := []; end; +# Called from C by GAP_OBJ_TO_JSON_STRING to serialise objects it does not +# handle directly (floats, large integers, component objects, and any types +# with a user-installed _GapToJsonStreamInternal method). +_JSON_ObjToString := function(obj) + local str, s; + str := ""; + s := OutputTextString(str, true); + SetPrintFormattingStatus(s, false); + _GapToJsonStreamInternal(s, obj); + return str; +end; + InstallMethod(_GapToJsonStreamInternal, [IsOutputStream, IsInt], function(o, d) WriteAll(o, STRING_INT(d)); @@ -114,12 +126,7 @@ InstallGlobalFunction(GapToJsonString, function(obj) - local str, s; - str := ""; - s := OutputTextString(str, true); - SetPrintFormattingStatus(s, false); - GapToJsonStream(s, obj); - return str; + return GAP_OBJ_TO_JSON_STRING(obj); end ); InstallGlobalFunction(JsonStringToGap, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/json-2.4.0/src/json.cc new/json-2.5.0/src/json.cc --- old/json-2.4.0/src/json.cc 2026-05-08 02:00:00.000000000 +0200 +++ new/json-2.5.0/src/json.cc 2026-09-25 02:00:00.000000000 +0200 @@ -9,7 +9,11 @@ #include "picojson/picojson.h" #include "picojson/gap-traits.h" +#include <cstdlib> +#include <cstring> + static Obj _GapToJsonStreamInternal; +static Obj _JSON_ObjToString; typedef picojson::value_t<gap_type_traits> gmp_value; @@ -69,8 +73,13 @@ return 4; } -static Int getChar(Obj list, Int pos) +// Reading past the end of <list> yields 0, which is not a continuation byte, +// so a truncated multi-byte sequence at the end of the string falls back to +// Latin-1 like any other malformed one. +static Int getChar(Obj list, Int len, Int pos) { + if (pos > len) + return 0; Obj c = ELM_LIST(list, pos); if (c == NULL) return 0; @@ -78,20 +87,20 @@ return *((UChar *)ADDR_OBJ(c)); } -static Int getUTF8Char(Obj list, Int * basepos) +static Int getUTF8Char(Obj list, Int len, Int * basepos) { Int pos = *basepos; - UInt val = getChar(list, pos); + UInt val = getChar(list, len, pos); UInt singlebyte_val = val; - Int len = numberOfBytes(val); + Int nbytes = numberOfBytes(val); pos++; - if (len == 1) { + if (nbytes == 1) { *basepos = pos; return val; } - switch (len) { + switch (nbytes) { case 2: val = val & 0x3F; if (val & 0x20) @@ -112,8 +121,8 @@ } val = val & 0x3F; - for (Int i = 1; i < len; ++i) { - UInt c = getChar(list, pos); + for (Int i = 1; i < nbytes; ++i) { + UInt c = getChar(list, len, pos); if ((c & 0xC0) != 0x80) goto invalid; val = (val << 6) | (c & 0x3F); @@ -160,31 +169,36 @@ return s; } -static Obj FuncJSON_ESCAPE_STRING(Obj self, Obj param) +// Does <param> (a string, possibly not in string rep) contain any character +// that JSON string output must escape or re-encode? +static Int jsonNeedsEscaping(Obj param, Int lenString) { - if(!IS_STRING(param)) - { - ErrorQuit("Input to JsonEscapeString must be a string", 0, 0); - } - - Int needEscaping = 0; - Int lenString = LEN_LIST(param); - for (Int i = 1; i <= lenString && needEscaping == 0; ++i) { + for (Int i = 1; i <= lenString; ++i) { Obj gapchar = ELMW_LIST(param, i); UChar u = *((UChar*)ADDR_OBJ(gapchar)); switch(u) { case '\\': case '"': case '/': case '\b': case '\t': case '\n': case '\f': case '\r': - needEscaping = 1; - break; + return 1; default: if (u < ' ' || u >= 128) - needEscaping = 1; + return 1; } } + return 0; +} - if (needEscaping == 0) +static Obj FuncJSON_ESCAPE_STRING(Obj self, Obj param) +{ + if(!IS_STRING(param)) + { + ErrorQuit("Input to JsonEscapeString must be a string", 0, 0); + } + + Int lenString = LEN_LIST(param); + + if (jsonNeedsEscaping(param, lenString) == 0) return param; // Massively over-long string @@ -193,7 +207,7 @@ UChar * out = base; Int i = 1; while (i <= lenString) { - Int u = getUTF8Char(param, &i); + Int u = getUTF8Char(param, lenString, &i); switch(u) { case '\\': case '"': case '/': @@ -255,6 +269,240 @@ return 0; } +/****************************************************************************** +** Direct-to-string JSON serialiser used by GapToJsonString. +** +** GapToJsonString owns its output string, so instead of writing through an +** OutputTextString + AppendCStr we manage the string as a raw buffer: we +** over-allocate, write directly, and shrink to the exact length at the end. +** +** GC discipline: a raw pointer into a GAP string bag is invalidated by any +** allocation, so we never *store* one. The buffer state is the string bag +** handle (stable across GC) plus the length and capacity as plain integers +** (GC-immune). Every write primitive calls jbuf_ensure first, then fetches +** CHARS_STRING(str) freshly and writes with no allocation in between. This +** means callers may allocate/GC freely between primitives (e.g. the fallback +** below) with nothing to re-derive. +******************************************************************************/ + +struct JBuf { + Obj str; // the growing GAP string bag (handle stable across GC) + size_t len; // bytes written so far + size_t cap; // capacity in bytes (== the length passed to GROW_STRING) +}; + +// Ensure room for `need` more bytes. May allocate (and GC); callers must hold +// no raw string pointer across this call, only the JBuf. +static inline void jbuf_ensure(JBuf * b, size_t need) +{ + if (b->cap - b->len < need) { + size_t newcap = b->cap ? b->cap : 256; + while (newcap - b->len < need) + newcap *= 2; + GROW_STRING(b->str, newcap); + b->cap = newcap; + } +} + +static inline void jbuf_putc(JBuf * b, char c) +{ + jbuf_ensure(b, 1); + CHARS_STRING(b->str)[b->len++] = (UChar)c; +} + +static inline void jbuf_putlit(JBuf * b, const char * s, size_t n) +{ + jbuf_ensure(b, n); + memcpy(CHARS_STRING(b->str) + b->len, s, n); + b->len += n; +} + +// Append <str> (must be in string rep) as a quoted, escaped JSON string. +static void jbuf_json_string(JBuf * b, Obj str) +{ + Int len = LEN_LIST(str); + + if (jsonNeedsEscaping(str, len) == 0) { + // No escaping needed: quote + raw bytes. Fetch both string pointers + // *after* jbuf_ensure, in the same no-allocation region. + jbuf_ensure(b, (size_t)len + 2); + UChar * out = CHARS_STRING(b->str) + b->len; + *out++ = '"'; + memcpy(out, CONST_CSTR_STRING(str), len); + out += len; + *out++ = '"'; + b->len += (size_t)len + 2; + return; + } + + // Worst case is 6 bytes per input byte (\uXXXX). Reserve once so the walk + // below allocates nothing; base/out stay valid throughout. + jbuf_ensure(b, (size_t)len * 6 + 2); + UChar * base = CHARS_STRING(b->str); + UChar * out = base + b->len; + *out++ = '"'; + Int i = 1; + while (i <= len) { + Int u = getUTF8Char(str, len, &i); + switch(u) + { + case '\\': case '"': case '/': + out[0] = '\\'; + out[1] = u; + out += 2; + break; +#define ESCAPE_CASE(x,y) case x: out[0] = '\\'; out[1] = y; out += 2; break; + ESCAPE_CASE('\b', 'b'); + ESCAPE_CASE('\t', 't'); + ESCAPE_CASE('\n', 'n'); + ESCAPE_CASE('\f', 'f'); + ESCAPE_CASE('\r', 'r'); +#undef ESCAPE_CASE + default: + if(u < ' ') + { + snprintf((char*)out, 7, "\\u%04X", (unsigned)u); + out += 6; + } + else + { + out = outputUnicodeChar(out, u); + } + } + } + *out++ = '"'; + b->len = out - base; +} + +// Record fields sorted by name, for byte-stable output (matches Set(RecNames)). +struct JsonFieldRef { + const UChar * name; + UInt len; + UInt pos; +}; + +static int jsonFieldRefCmp(const void * a, const void * b) +{ + const JsonFieldRef * fa = (const JsonFieldRef *)a; + const JsonFieldRef * fb = (const JsonFieldRef *)b; + UInt m = fa->len < fb->len ? fa->len : fb->len; + int c = memcmp(fa->name, fb->name, m); + if (c != 0) + return c; + if (fa->len != fb->len) + return fa->len < fb->len ? -1 : 1; + return 0; +} + +static void serialiseJson(JBuf * b, Obj obj); + +static void serialiseJsonRecord(JBuf * b, Obj rec) +{ + // Sort (and de-duplicate) the record's components, exactly as RecNames + // does. Afterwards all stored rnams are negated (the "sorted" marker), so + // the true rnam is -GET_RNAM_PREC(...). Without this, freshly built records + // can hold positive/unsorted rnams and NAME_RNAM indexes out of range. + SortPRecRNam(rec); + UInt n = LEN_PREC(rec); + jbuf_putc(b, '{'); + if (n > 0) { + JsonFieldRef * fields = (JsonFieldRef *)malloc(n * sizeof(JsonFieldRef)); + for (UInt i = 1; i <= n; ++i) { + Obj nm = NAME_RNAM(-GET_RNAM_PREC(rec, i)); + fields[i - 1].name = CONST_CHARS_STRING(nm); + fields[i - 1].len = GET_LEN_STRING(nm); + fields[i - 1].pos = i; + } + // No GAP allocation happens during the sort, so the cached name + // pointers stay valid here. + qsort(fields, n, sizeof(JsonFieldRef), jsonFieldRefCmp); + for (UInt k = 0; k < n; ++k) { + if (k != 0) + jbuf_putc(b, ','); + // Re-fetch the name Obj fresh: emitting the previous field may have + // grown the buffer (GC), invalidating cached name pointers. + jbuf_json_string(b, NAME_RNAM(-GET_RNAM_PREC(rec, fields[k].pos))); + jbuf_putlit(b, " : ", 3); + serialiseJson(b, GET_ELM_PREC(rec, fields[k].pos)); + } + free(fields); + } + jbuf_putc(b, '}'); +} + +static void serialiseJson(JBuf * b, Obj obj) +{ + if (IS_INTOBJ(obj)) { + char tmp[32]; + int m = snprintf(tmp, sizeof(tmp), "%ld", (long)INT_INTOBJ(obj)); + jbuf_putlit(b, tmp, m); + return; + } + if (obj == True) { jbuf_putlit(b, "true", 4); return; } + if (obj == False) { jbuf_putlit(b, "false", 5); return; } + if (obj == Fail) { jbuf_putlit(b, "null", 4); return; } + + if (IS_STRING(obj)) { + Int len = LEN_LIST(obj); + if (len == 0) { + // Matches the GAP IsString method: "" for string rep, [] otherwise. + if (IS_STRING_REP(obj)) + jbuf_putlit(b, "\"\"", 2); + else + jbuf_putlit(b, "[]", 2); + return; + } + if (!IS_STRING_REP(obj)) + obj = CopyToStringRep(obj); // allocates; obj is local, safe after + jbuf_json_string(b, obj); + return; + } + + if (IS_LIST(obj)) { + RequireDenseList("GapToJsonString", obj); + Int len = LEN_LIST(obj); + jbuf_putc(b, '['); + for (Int i = 1; i <= len; ++i) { + if (i != 1) + jbuf_putc(b, ','); + serialiseJson(b, ELMW_LIST(obj, i)); + } + jbuf_putc(b, ']'); + return; + } + + if (IS_PREC(obj)) { + serialiseJsonRecord(b, obj); + return; + } + + // Fallback for everything else (floats, large integers, component objects, + // user-installed methods): let the GAP operation produce the JSON string, + // then copy it in. The call may GC, but we hold only the JBuf (bag handle + // + integer offsets), so there is nothing to re-derive. + { + Obj s = CALL_1ARGS(_JSON_ObjToString, obj); + Int slen = GET_LEN_STRING(s); + jbuf_ensure(b, slen); // may move the bags; fetch both pointers after + memcpy(CHARS_STRING(b->str) + b->len, CONST_CSTR_STRING(s), slen); + b->len += slen; + } +} + +static Obj FuncGAP_OBJ_TO_JSON_STRING(Obj self, Obj obj) +{ + JBuf b; + b.str = NEW_STRING(256); + b.len = 0; + b.cap = 256; + + serialiseJson(&b, obj); + + SET_LEN_STRING(b.str, b.len); + SHRINK_STRING(b.str); + return b.str; +} + // WARNING: This class is only complete enough to work with // picojson's iterator support. struct GapStreamToInputIterator @@ -348,7 +596,7 @@ bool ungotc_check = false; picojson::parse(v, GapStreamToInputIterator(stream), endGapStreamIterator(), &err, &ungotc_check); if (! err.empty()) { - ErrorQuit(err.c_str(), 0, 0); + ErrorQuit("%s", (Int)err.c_str(), 0); return Fail; } @@ -429,7 +677,7 @@ // char* res = picojson::parse(v, ptr, ptrend, &err, &ungotc_check); if (! err.empty()) { - ErrorQuit(err.c_str(), 0, 0); + ErrorQuit("%s", (Int)err.c_str(), 0); return Fail; } @@ -464,7 +712,8 @@ GVAR_FUNC_1ARGS(JSON_ESCAPE_STRING, string), GVAR_FUNC_1ARGS(JSON_STREAM_TO_GAP, string), GVAR_FUNC_3ARGS(GAP_LIST_TO_JSON_STRING, string, stream, list), - + GVAR_FUNC_1ARGS(GAP_OBJ_TO_JSON_STRING, obj), + { 0 } /* Finish with an empty entry */ }; @@ -478,6 +727,7 @@ InitHdlrFuncsFromTable( GVarFuncs ); ImportGVarFromLibrary("_GapToJsonStreamInternal", &_GapToJsonStreamInternal); + ImportGVarFromLibrary("_JSON_ObjToString", &_JSON_ObjToString); /* return success */ return 0; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/json-2.4.0/tst/test_hard.tst new/json-2.5.0/tst/test_hard.tst --- old/json-2.4.0/tst/test_hard.tst 2026-05-08 02:00:00.000000000 +0200 +++ new/json-2.5.0/tst/test_hard.tst 2026-09-25 02:00:00.000000000 +0200 @@ -7,3 +7,13 @@ gap> sout := StringFile(fout);; gap> json = sout; true + +# A truncated multi-byte sequence at the very end of a string used to make +# getUTF8Char read past the end of the buffer. Like any other malformed +# sequence, its bytes are now treated as Latin-1 and re-encoded. +gap> List(GapToJsonString([ CHAR_INT(200) ]), IntChar); +[ 34, 195, 136, 34 ] +gap> List(GapToJsonString([ CHAR_INT(226), CHAR_INT(130) ]), IntChar); +[ 34, 195, 162, 194, 130, 34 ] +gap> ForAll([0..255], b -> IsString(GapToJsonString([ CHAR_INT(b) ]))); +true
