From 234e0836f1040f7724251b4120a2351bcbf64131 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Sat, 13 Aug 2022 15:11:17 -0600 Subject: set up as a separate repo --- COPYING.apache-2.0.txt | 202 -- COPYING.bsd3.txt | 27 + COPYING.gpl-3.0.txt | 674 ----- COPYING.txt | 36 +- LICENSE | 1 + Makefile | 12 - adapter_test.go | 120 + base64.go | 121 + base64_test.go | 44 + borrowed_decode_test.go | 2590 ++++++++++++++++++++ borrowed_encode_test.go | 1208 +++++++++ borrowed_fuzz_test.go | 83 + borrowed_misc.go | 42 + borrowed_scanner_test.go | 305 +++ borrowed_tagkey_test.go | 121 + borrowed_tags.go | 38 + borrowed_tags_test.go | 28 + decode.go | 812 ++++++ encode.go | 333 +++ go.mod | 11 + go.sum | 15 + lib/lowmemjson.stock/borrowed_tags_test.go | 28 - lib/lowmemjson/adapter_test.go | 120 - lib/lowmemjson/base64.go | 121 - lib/lowmemjson/base64_test.go | 44 - lib/lowmemjson/borrowed_decode_test.go | 2590 -------------------- lib/lowmemjson/borrowed_encode_test.go | 1208 --------- lib/lowmemjson/borrowed_fuzz_test.go | 83 - lib/lowmemjson/borrowed_misc.go | 42 - lib/lowmemjson/borrowed_scanner_test.go | 305 --- lib/lowmemjson/borrowed_tagkey_test.go | 121 - lib/lowmemjson/borrowed_tags.go | 38 - lib/lowmemjson/borrowed_tags_test.go | 28 - lib/lowmemjson/decode.go | 812 ------ lib/lowmemjson/encode.go | 333 --- lib/lowmemjson/misc.go | 131 - lib/lowmemjson/reencode.go | 598 ----- lib/lowmemjson/struct.go | 164 -- ...ad7f1a4606dc7419750995a57828aa25ea57fe7099d5c03 | 2 - ...5cac0aa0f3b43ec1c904414fa6d38f6fc288b0bbd69588a | 2 - ...a3590a86b406b9f2565987a4a3b6d7660ddc308b5b2fae2 | 2 - ...3cef81330f1d92060be4d694a93dedd654bf48743a7d2bd | 2 - ...775199a43e0f9fd5c94bba343ce7bb6724d4ebafe311ed4 | 2 - ...1b66b08f0a4ff81edd7cb53d00dce8ee0eaf31683996026 | 2 - ...be1ceaa4a53e4de01a04efc02ac9cfda60f9815f80e9b9d | 2 - ...f6ee8adaa585d4f6a01f359a04737e51ffc70f16f480b9b | 2 - ...920b5202cd1269174416ce32769c7f59376e76b7dd3129c | 2 - ...7433233f3a90099024e580a6ba319ea2bf539880c50bd7c | 2 - ...326638b8915f80863feab0ba0108183b3093934bdc0420c | 2 - ...aeaae3fe39f5f2ff9830777253ff371c5ef6f403a0f8f0f | 2 - ...7b7fe654ff46010d6fa76f0a142c3523c42454f8ad10b07 | 2 - ...5bd136874415dddfff5c586e662f21420caa7a94131a56a | 2 - ...d54218d2ad8112204672cc1fb30be297853616788208a5c | 2 - ...eab217543561dfd8001d4a44f53ceb664aaba86cebfaf21 | 2 - ...77408be5ef9389790e33ed1886073dec445d4cf05bcd4b4 | 2 - ...1fc4dbf537d4d81f389524539f402d13aa01f93a65ac7e9 | 2 - ...c70baf6d7821a5a6f3a90cabb033575790be91723593680 | 2 - ...9f75a3b303897c59b11e4bfb7622f25ff251a92f182bc2a | 2 - ...27531927c5c1e65d159b70f39cd161da0dba348c1221ab3 | 2 - ...1a201281dbf6568628b4135c35c811dd9bce97620a75d43 | 2 - misc.go | 131 + reencode.go | 598 +++++ struct.go | 164 ++ ...ad7f1a4606dc7419750995a57828aa25ea57fe7099d5c03 | 2 + ...5cac0aa0f3b43ec1c904414fa6d38f6fc288b0bbd69588a | 2 + ...a3590a86b406b9f2565987a4a3b6d7660ddc308b5b2fae2 | 2 + ...3cef81330f1d92060be4d694a93dedd654bf48743a7d2bd | 2 + ...775199a43e0f9fd5c94bba343ce7bb6724d4ebafe311ed4 | 2 + ...1b66b08f0a4ff81edd7cb53d00dce8ee0eaf31683996026 | 2 + ...be1ceaa4a53e4de01a04efc02ac9cfda60f9815f80e9b9d | 2 + ...f6ee8adaa585d4f6a01f359a04737e51ffc70f16f480b9b | 2 + ...920b5202cd1269174416ce32769c7f59376e76b7dd3129c | 2 + ...7433233f3a90099024e580a6ba319ea2bf539880c50bd7c | 2 + ...326638b8915f80863feab0ba0108183b3093934bdc0420c | 2 + ...aeaae3fe39f5f2ff9830777253ff371c5ef6f403a0f8f0f | 2 + ...7b7fe654ff46010d6fa76f0a142c3523c42454f8ad10b07 | 2 + ...5bd136874415dddfff5c586e662f21420caa7a94131a56a | 2 + ...d54218d2ad8112204672cc1fb30be297853616788208a5c | 2 + ...eab217543561dfd8001d4a44f53ceb664aaba86cebfaf21 | 2 + ...77408be5ef9389790e33ed1886073dec445d4cf05bcd4b4 | 2 + ...1fc4dbf537d4d81f389524539f402d13aa01f93a65ac7e9 | 2 + ...c70baf6d7821a5a6f3a90cabb033575790be91723593680 | 2 + ...9f75a3b303897c59b11e4bfb7622f25ff251a92f182bc2a | 2 + ...27531927c5c1e65d159b70f39cd161da0dba348c1221ab3 | 2 + ...1a201281dbf6568628b4135c35c811dd9bce97620a75d43 | 2 + tools/.gitignore | 1 + 86 files changed, 6846 insertions(+), 7725 deletions(-) delete mode 100644 COPYING.apache-2.0.txt create mode 100644 COPYING.bsd3.txt delete mode 100644 COPYING.gpl-3.0.txt create mode 120000 LICENSE create mode 100644 adapter_test.go create mode 100644 base64.go create mode 100644 base64_test.go create mode 100644 borrowed_decode_test.go create mode 100644 borrowed_encode_test.go create mode 100644 borrowed_fuzz_test.go create mode 100644 borrowed_misc.go create mode 100644 borrowed_scanner_test.go create mode 100644 borrowed_tagkey_test.go create mode 100644 borrowed_tags.go create mode 100644 borrowed_tags_test.go create mode 100644 decode.go create mode 100644 encode.go create mode 100644 go.mod create mode 100644 go.sum delete mode 100644 lib/lowmemjson.stock/borrowed_tags_test.go delete mode 100644 lib/lowmemjson/adapter_test.go delete mode 100644 lib/lowmemjson/base64.go delete mode 100644 lib/lowmemjson/base64_test.go delete mode 100644 lib/lowmemjson/borrowed_decode_test.go delete mode 100644 lib/lowmemjson/borrowed_encode_test.go delete mode 100644 lib/lowmemjson/borrowed_fuzz_test.go delete mode 100644 lib/lowmemjson/borrowed_misc.go delete mode 100644 lib/lowmemjson/borrowed_scanner_test.go delete mode 100644 lib/lowmemjson/borrowed_tagkey_test.go delete mode 100644 lib/lowmemjson/borrowed_tags.go delete mode 100644 lib/lowmemjson/borrowed_tags_test.go delete mode 100644 lib/lowmemjson/decode.go delete mode 100644 lib/lowmemjson/encode.go delete mode 100644 lib/lowmemjson/misc.go delete mode 100644 lib/lowmemjson/reencode.go delete mode 100644 lib/lowmemjson/struct.go delete mode 100644 lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/06e2c9db80a08b67fad7f1a4606dc7419750995a57828aa25ea57fe7099d5c03 delete mode 100644 lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/24f53a36f8832fec65cac0aa0f3b43ec1c904414fa6d38f6fc288b0bbd69588a delete mode 100644 lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/2d49311ef22319f70a3590a86b406b9f2565987a4a3b6d7660ddc308b5b2fae2 delete mode 100644 lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/356e28f5914a0f16f3cef81330f1d92060be4d694a93dedd654bf48743a7d2bd delete mode 100644 lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/582528ddfad69eb57775199a43e0f9fd5c94bba343ce7bb6724d4ebafe311ed4 delete mode 100644 lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/60c81ee499a7f1e151b66b08f0a4ff81edd7cb53d00dce8ee0eaf31683996026 delete mode 100644 lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/66498f377f38b53eebe1ceaa4a53e4de01a04efc02ac9cfda60f9815f80e9b9d delete mode 100644 lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/731951fe84fa6f3a7f6ee8adaa585d4f6a01f359a04737e51ffc70f16f480b9b delete mode 100644 lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/7d6367ba84cd18550920b5202cd1269174416ce32769c7f59376e76b7dd3129c delete mode 100644 lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/8727b16d337d7b8187433233f3a90099024e580a6ba319ea2bf539880c50bd7c delete mode 100644 lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/9201a772731543760326638b8915f80863feab0ba0108183b3093934bdc0420c delete mode 100644 lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/92f75f690317ace34aeaae3fe39f5f2ff9830777253ff371c5ef6f403a0f8f0f delete mode 100644 lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/93d6f7bc0d93f998c7b7fe654ff46010d6fa76f0a142c3523c42454f8ad10b07 delete mode 100644 lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/a7450fd77fc7c53cc5bd136874415dddfff5c586e662f21420caa7a94131a56a delete mode 100644 lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/a95d2a0f87501a643d54218d2ad8112204672cc1fb30be297853616788208a5c delete mode 100644 lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/beed435aa2fee4819eab217543561dfd8001d4a44f53ceb664aaba86cebfaf21 delete mode 100644 lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/c2501043394e49f2477408be5ef9389790e33ed1886073dec445d4cf05bcd4b4 delete mode 100644 lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/caf81e9797b19c76c1fc4dbf537d4d81f389524539f402d13aa01f93a65ac7e9 delete mode 100644 lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/cc90a4a40ae9b3beac70baf6d7821a5a6f3a90cabb033575790be91723593680 delete mode 100644 lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/ec72f669d648d8d9b9f75a3b303897c59b11e4bfb7622f25ff251a92f182bc2a delete mode 100644 lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/f34630c44c11bb13d27531927c5c1e65d159b70f39cd161da0dba348c1221ab3 delete mode 100644 lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/fd67efb09d433a1351a201281dbf6568628b4135c35c811dd9bce97620a75d43 create mode 100644 misc.go create mode 100644 reencode.go create mode 100644 struct.go create mode 100644 testdata/fuzz/FuzzBase64Decoder/06e2c9db80a08b67fad7f1a4606dc7419750995a57828aa25ea57fe7099d5c03 create mode 100644 testdata/fuzz/FuzzBase64Decoder/24f53a36f8832fec65cac0aa0f3b43ec1c904414fa6d38f6fc288b0bbd69588a create mode 100644 testdata/fuzz/FuzzBase64Decoder/2d49311ef22319f70a3590a86b406b9f2565987a4a3b6d7660ddc308b5b2fae2 create mode 100644 testdata/fuzz/FuzzBase64Decoder/356e28f5914a0f16f3cef81330f1d92060be4d694a93dedd654bf48743a7d2bd create mode 100644 testdata/fuzz/FuzzBase64Decoder/582528ddfad69eb57775199a43e0f9fd5c94bba343ce7bb6724d4ebafe311ed4 create mode 100644 testdata/fuzz/FuzzBase64Decoder/60c81ee499a7f1e151b66b08f0a4ff81edd7cb53d00dce8ee0eaf31683996026 create mode 100644 testdata/fuzz/FuzzBase64Decoder/66498f377f38b53eebe1ceaa4a53e4de01a04efc02ac9cfda60f9815f80e9b9d create mode 100644 testdata/fuzz/FuzzBase64Decoder/731951fe84fa6f3a7f6ee8adaa585d4f6a01f359a04737e51ffc70f16f480b9b create mode 100644 testdata/fuzz/FuzzBase64Decoder/7d6367ba84cd18550920b5202cd1269174416ce32769c7f59376e76b7dd3129c create mode 100644 testdata/fuzz/FuzzBase64Decoder/8727b16d337d7b8187433233f3a90099024e580a6ba319ea2bf539880c50bd7c create mode 100644 testdata/fuzz/FuzzBase64Decoder/9201a772731543760326638b8915f80863feab0ba0108183b3093934bdc0420c create mode 100644 testdata/fuzz/FuzzBase64Decoder/92f75f690317ace34aeaae3fe39f5f2ff9830777253ff371c5ef6f403a0f8f0f create mode 100644 testdata/fuzz/FuzzBase64Decoder/93d6f7bc0d93f998c7b7fe654ff46010d6fa76f0a142c3523c42454f8ad10b07 create mode 100644 testdata/fuzz/FuzzBase64Decoder/a7450fd77fc7c53cc5bd136874415dddfff5c586e662f21420caa7a94131a56a create mode 100644 testdata/fuzz/FuzzBase64Decoder/a95d2a0f87501a643d54218d2ad8112204672cc1fb30be297853616788208a5c create mode 100644 testdata/fuzz/FuzzBase64Decoder/beed435aa2fee4819eab217543561dfd8001d4a44f53ceb664aaba86cebfaf21 create mode 100644 testdata/fuzz/FuzzBase64Decoder/c2501043394e49f2477408be5ef9389790e33ed1886073dec445d4cf05bcd4b4 create mode 100644 testdata/fuzz/FuzzBase64Decoder/caf81e9797b19c76c1fc4dbf537d4d81f389524539f402d13aa01f93a65ac7e9 create mode 100644 testdata/fuzz/FuzzBase64Decoder/cc90a4a40ae9b3beac70baf6d7821a5a6f3a90cabb033575790be91723593680 create mode 100644 testdata/fuzz/FuzzBase64Decoder/ec72f669d648d8d9b9f75a3b303897c59b11e4bfb7622f25ff251a92f182bc2a create mode 100644 testdata/fuzz/FuzzBase64Decoder/f34630c44c11bb13d27531927c5c1e65d159b70f39cd161da0dba348c1221ab3 create mode 100644 testdata/fuzz/FuzzBase64Decoder/fd67efb09d433a1351a201281dbf6568628b4135c35c811dd9bce97620a75d43 create mode 100644 tools/.gitignore diff --git a/COPYING.apache-2.0.txt b/COPYING.apache-2.0.txt deleted file mode 100644 index d645695..0000000 --- a/COPYING.apache-2.0.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/COPYING.bsd3.txt b/COPYING.bsd3.txt new file mode 100644 index 0000000..6a66aea --- /dev/null +++ b/COPYING.bsd3.txt @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/COPYING.gpl-3.0.txt b/COPYING.gpl-3.0.txt deleted file mode 100644 index f288702..0000000 --- a/COPYING.gpl-3.0.txt +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/COPYING.txt b/COPYING.txt index d8b9862..68e2750 100644 --- a/COPYING.txt +++ b/COPYING.txt @@ -1,29 +1,10 @@ -btrfs-progs-ng - Better btrfs userspace tools +lowmemjson - A streaming json library with minimal memory footprint Copyright (C) 2022 Luke Shumaker This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 3 of the License, or (at -your option) any later version. Individual source files may be -available (at your option) under an alternative license. - -Most of the source code is licensed under the terms of the GNU General -Public License as published by the Free Software Foundation; either -version 2 of the License, or (at your option) any later version. A -small minority of the source code is licensed under the terms of the -Apache License, version 2.0. Refer to the "SPDX-License-Identifier" -comment in each file to see which license it is made available under. - - > The terms of the GNU General Public License version 2 and terms of - > the Apache License version 2.0 are contradictory--they cannot both - > be fulfilled at the same time; but the terms of the GNU General - > Public License version 3 and the Apache License version 2.0 are not - > contradictory and can be fulfilled at the same time. Because of - > this one must take the "or (at your option) any later version" on - > the General Public License and treat the combined work as being - > under the GNU General Public License version 3 or later. This is - > why the opening paragraph identified the program as being available - > under this version. +the Free Software Foundation; either version 2 of the License, or (at +your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -31,8 +12,9 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. Along with this program, you should have received a copy of the GNU -General Public License (in the COPYING.gpl-2.0.txt and -COPYING.gpl-3.0.txt files) and the Apache License (in the -COPYING.apache-2.0.txt file). If not, see - and -. +General Public License (in the COPYING.gpl-2.0.txt file). If not, see +. + +Parts of the program (the files starting with "borrowed_") are adapted +from the Go standard library, and are subject to the terms of the +3-clause BSD license in COPYING.bsd3.txt. diff --git a/LICENSE b/LICENSE new file mode 120000 index 0000000..910a6ac --- /dev/null +++ b/LICENSE @@ -0,0 +1 @@ +COPYING.bsd3.txt \ No newline at end of file diff --git a/Makefile b/Makefile index c6cb224..d825ce9 100644 --- a/Makefile +++ b/Makefile @@ -4,10 +4,6 @@ # main -build: - go build -o bin/ ./cmd/... -.PHONY: build - check: go test -race ./... .PHONY: check @@ -19,25 +15,17 @@ lint: tools/bin/golangci-lint # generate generate/files = COPYING.gpl-2.0.txt -generate/files += COPYING.gpl-3.0.txt -generate/files += COPYING.apache-2.0.txt generate: generate-clean - $(MAKE) -C lib/btrfs $(MAKE) $(generate/files) .PHONY: generate generate-clean: - $(MAKE) -C lib/btrfs clean rm -f $(generate/files) .PHONY: generate-clean COPYING.gpl-2.0.txt: curl https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt > $@ -COPYING.gpl-3.0.txt: - curl https://www.gnu.org/licenses/gpl-3.0.txt > $@ -COPYING.apache-2.0.txt: - curl https://apache.org/licenses/LICENSE-2.0.txt > $@ # tools diff --git a/adapter_test.go b/adapter_test.go new file mode 100644 index 0000000..b1aec3e --- /dev/null +++ b/adapter_test.go @@ -0,0 +1,120 @@ +// Copyright (C) 2022 Luke Shumaker +// +// SPDX-License-Identifier: GPL-2.0-or-later + +package lowmemjson + +import ( + "bufio" + "bytes" + "encoding/json" + "io" +) + +func MarshalIndent(v any, prefix, indent string) ([]byte, error) { + var buf bytes.Buffer + formatter := &ReEncoder{ + Out: &buf, + Indent: indent, + prefix: prefix, + } + err := Encode(formatter, v) + return buf.Bytes(), err +} + +func Marshal(v any) ([]byte, error) { + var buf bytes.Buffer + formatter := &ReEncoder{ + Out: &buf, + Compact: true, + } + err := Encode(formatter, v) + return buf.Bytes(), err +} + +func HTMLEscape(dst *bytes.Buffer, src []byte) { + formatter := &ReEncoder{ + Out: dst, + } + _, _ = formatter.Write(src) +} + +func Compact(dst *bytes.Buffer, src []byte) error { + formatter := &ReEncoder{ + Out: dst, + Compact: true, + } + _, err := formatter.Write(src) + return err +} + +func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error { + formatter := &ReEncoder{ + Out: dst, + Indent: indent, + prefix: prefix, + } + _, err := formatter.Write(src) + return err +} + +func Valid(data []byte) bool { + formatter := &ReEncoder{ + Out: io.Discard, + Compact: true, + } + _, err := formatter.Write(data) + return err == nil +} + +func Unmarshal(data []byte, ptr any) error { + return Decode(bytes.NewReader(data), ptr) +} + +func init() { + forceBufio = true +} + +func (dec *Decoder) Buffered() io.Reader { + buf := dec.r.(*bufio.Reader) + dat, _ := buf.Peek(buf.Buffered()) + return bytes.NewReader(dat) +} + +//func (dec *Decoder) Token() (Token, error) + +///////////////////////////////////////////////////////////////////// + +type ( + Number = json.Number + Marshaler = json.Marshaler + RawMessage = json.RawMessage + + UnsupportedValueError = json.UnsupportedValueError + MarshalerError = json.MarshalerError + UnmarshalTypeError = json.UnmarshalTypeError +) + +const ( + startDetectingCyclesAfter = 1000 +) + +func isSpace(c byte) bool { + switch c { + case 0x0020, 0x000A, 0x000D, 0x0009: + return true + default: + return false + } +} + +type encodeState struct { + bytes.Buffer +} + +func (es *encodeState) string(str string, _ bool) { + encodeString(&es.Buffer, str) +} +func (es *encodeState) stringBytes(str []byte, _ bool) { + encodeString(&es.Buffer, str) +} diff --git a/base64.go b/base64.go new file mode 100644 index 0000000..86fc293 --- /dev/null +++ b/base64.go @@ -0,0 +1,121 @@ +// Copyright (C) 2022 Luke Shumaker +// +// SPDX-License-Identifier: GPL-2.0-or-later + +package lowmemjson + +import ( + "encoding/base64" + "io" + "strings" +) + +type base64Decoder struct { + dst io.Writer + + err error + pos int64 + buf [4]byte + bufLen int +} + +func newBase64Decoder(w io.Writer) io.WriteCloser { + return &base64Decoder{ + dst: w, + } +} + +func (dec *base64Decoder) decodeByte(b byte) (byte, bool) { + const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + n := strings.IndexByte(alphabet, b) + if n < 0 { + return 0, false + } + dec.pos++ + return byte(n), true +} + +func (dec *base64Decoder) decodeTuple(a, b, c, d byte) error { + var decodedLen int + var encoded [4]byte + var ok bool + + if a != '=' { + encoded[0], ok = dec.decodeByte(a) + if !ok { + return base64.CorruptInputError(dec.pos) + } + decodedLen++ + } + if b != '=' { + encoded[1], ok = dec.decodeByte(b) + if !ok { + return base64.CorruptInputError(dec.pos) + } + // do NOT increment decodedLen here + } + if c != '=' { + encoded[2], ok = dec.decodeByte(c) + if !ok { + return base64.CorruptInputError(dec.pos) + } + decodedLen++ + } + if d != '=' { + encoded[3], ok = dec.decodeByte(d) + if !ok { + return base64.CorruptInputError(dec.pos) + } + decodedLen++ + } + + val := 0 | + uint32(encoded[0])<<18 | + uint32(encoded[1])<<12 | + uint32(encoded[2])<<6 | + uint32(encoded[3])<<0 + var decoded [3]byte + decoded[0] = byte(val >> 16) + decoded[1] = byte(val >> 8) + decoded[2] = byte(val >> 0) + + _, err := dec.dst.Write(decoded[:decodedLen]) + return err +} + +func (dec *base64Decoder) Write(dat []byte) (int, error) { + if len(dat) == 0 { + return 0, nil + } + if dec.err != nil { + return 0, dec.err + } + var n int + if dec.bufLen > 0 { + n = copy(dec.buf[dec.bufLen:], dat) + dec.bufLen += n + if dec.bufLen < 4 { + return len(dat), nil + } + if err := dec.decodeTuple(dec.buf[0], dec.buf[1], dec.buf[2], dec.buf[3]); err != nil { + dec.err = err + return 0, dec.err + } + } + for ; n+3 < len(dat); n += 4 { + if err := dec.decodeTuple(dat[n], dat[n+1], dat[n+2], dat[n+3]); err != nil { + dec.err = err + return n, dec.err + } + } + dec.bufLen = copy(dec.buf[:], dat[n:]) + return len(dat), nil +} + +func (dec *base64Decoder) Close() error { + if dec.bufLen == 0 { + return nil + } + copy(dec.buf[:], "====") + return dec.decodeTuple(dec.buf[0], dec.buf[1], dec.buf[2], dec.buf[3]) +} diff --git a/base64_test.go b/base64_test.go new file mode 100644 index 0000000..43367af --- /dev/null +++ b/base64_test.go @@ -0,0 +1,44 @@ +// Copyright (C) 2022 Luke Shumaker +// +// SPDX-License-Identifier: GPL-2.0-or-later + +package lowmemjson + +import ( + "bytes" + "encoding/base64" + "testing" + + "github.com/stretchr/testify/require" +) + +func b64encode(t *testing.T, input []byte) []byte { + var encoded bytes.Buffer + enc := base64.NewEncoder(base64.StdEncoding, &encoded) + _, err := enc.Write(input) + require.NoError(t, err) + require.NoError(t, enc.Close()) + return encoded.Bytes() +} + +func b64decode(t *testing.T, input []byte) []byte { + var decoded bytes.Buffer + dec := newBase64Decoder(&decoded) + _, err := dec.Write(input) + require.NoError(t, err) + require.NoError(t, dec.Close()) + return decoded.Bytes() +} + +func FuzzBase64Decoder(f *testing.F) { + f.Fuzz(func(t *testing.T, input []byte) { + encoded := b64encode(t, input) + decoded := b64decode(t, encoded) + t.Logf("input b64 = %q", encoded) + t.Logf("expected decoded = %#v", input) + t.Logf("actual decoded = %#v", decoded) + if !bytes.Equal(input, decoded) { + t.Fail() + } + }) +} diff --git a/borrowed_decode_test.go b/borrowed_decode_test.go new file mode 100644 index 0000000..a1fd695 --- /dev/null +++ b/borrowed_decode_test.go @@ -0,0 +1,2590 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package lowmemjson // MODIFIED + +import ( + "bytes" + "encoding" + "errors" + "fmt" + "image" + "io" // MODIFIED + "math" + "math/big" + "net" + "reflect" + "strconv" + "strings" + "testing" + "time" +) + +type T struct { + X string + Y int + Z int `json:"-"` +} + +type U struct { + Alphabet string `json:"alpha"` +} + +type V struct { + F1 any + F2 int32 + F3 Number + F4 *VOuter +} + +type VOuter struct { + V V +} + +type W struct { + S SS +} + +type P struct { + PP PP +} + +type PP struct { + T T + Ts []T +} + +type SS string + +func (*SS) UnmarshalJSON(data []byte) error { + return &UnmarshalTypeError{Value: "number", Type: reflect.TypeOf(SS(""))} +} + +// ifaceNumAsFloat64/ifaceNumAsNumber are used to test unmarshaling with and +// without UseNumber +var ifaceNumAsFloat64 = map[string]any{ + "k1": float64(1), + "k2": "s", + "k3": []any{float64(1), float64(2.0), float64(3e-3)}, + "k4": map[string]any{"kk1": "s", "kk2": float64(2)}, +} + +var ifaceNumAsNumber = map[string]any{ + "k1": Number("1"), + "k2": "s", + "k3": []any{Number("1"), Number("2.0"), Number("3e-3")}, + "k4": map[string]any{"kk1": "s", "kk2": Number("2")}, +} + +type tx struct { + x int +} + +type u8 uint8 + +// A type that can unmarshal itself. + +type unmarshaler struct { + T bool +} + +func (u *unmarshaler) UnmarshalJSON(b []byte) error { + *u = unmarshaler{true} // All we need to see that UnmarshalJSON is called. + return nil +} + +type ustruct struct { + M unmarshaler +} + +type unmarshalerText struct { + A, B string +} + +// needed for re-marshaling tests +func (u unmarshalerText) MarshalText() ([]byte, error) { + return []byte(u.A + ":" + u.B), nil +} + +func (u *unmarshalerText) UnmarshalText(b []byte) error { + pos := bytes.IndexByte(b, ':') + if pos == -1 { + return errors.New("missing separator") + } + u.A, u.B = string(b[:pos]), string(b[pos+1:]) + return nil +} + +var _ encoding.TextUnmarshaler = (*unmarshalerText)(nil) + +type ustructText struct { + M unmarshalerText +} + +// u8marshal is an integer type that can marshal/unmarshal itself. +type u8marshal uint8 + +func (u8 u8marshal) MarshalText() ([]byte, error) { + return []byte(fmt.Sprintf("u%d", u8)), nil +} + +var errMissingU8Prefix = errors.New("missing 'u' prefix") + +func (u8 *u8marshal) UnmarshalText(b []byte) error { + if !bytes.HasPrefix(b, []byte{'u'}) { + return errMissingU8Prefix + } + n, err := strconv.Atoi(string(b[1:])) + if err != nil { + return err + } + *u8 = u8marshal(n) + return nil +} + +var _ encoding.TextUnmarshaler = (*u8marshal)(nil) + +var ( + umtrue = unmarshaler{true} + umslice = []unmarshaler{{true}} + umstruct = ustruct{unmarshaler{true}} + + umtrueXY = unmarshalerText{"x", "y"} + umsliceXY = []unmarshalerText{{"x", "y"}} + umstructXY = ustructText{unmarshalerText{"x", "y"}} + + ummapXY = map[unmarshalerText]bool{{"x", "y"}: true} +) + +// Test data structures for anonymous fields. + +type Point struct { + Z int +} + +type Top struct { + Level0 int + Embed0 + *Embed0a + *Embed0b `json:"e,omitempty"` // treated as named + Embed0c `json:"-"` // ignored + Loop + Embed0p // has Point with X, Y, used + Embed0q // has Point with Z, used + embed // contains exported field +} + +type Embed0 struct { + Level1a int // overridden by Embed0a's Level1a with json tag + Level1b int // used because Embed0a's Level1b is renamed + Level1c int // used because Embed0a's Level1c is ignored + Level1d int // annihilated by Embed0a's Level1d + Level1e int `json:"x"` // annihilated by Embed0a.Level1e +} + +type Embed0a struct { + Level1a int `json:"Level1a,omitempty"` + Level1b int `json:"LEVEL1B,omitempty"` + Level1c int `json:"-"` + Level1d int // annihilated by Embed0's Level1d + Level1f int `json:"x"` // annihilated by Embed0's Level1e +} + +type Embed0b Embed0 + +type Embed0c Embed0 + +type Embed0p struct { + image.Point +} + +type Embed0q struct { + Point +} + +type embed struct { + Q int +} + +type Loop struct { + Loop1 int `json:",omitempty"` + Loop2 int `json:",omitempty"` + *Loop +} + +// From reflect test: +// The X in S6 and S7 annihilate, but they also block the X in S8.S9. +type S5 struct { + S6 + S7 + S8 +} + +type S6 struct { + X int +} + +type S7 S6 + +type S8 struct { + S9 +} + +type S9 struct { + X int + Y int +} + +// From reflect test: +// The X in S11.S6 and S12.S6 annihilate, but they also block the X in S13.S8.S9. +type S10 struct { + S11 + S12 + S13 +} + +type S11 struct { + S6 +} + +type S12 struct { + S6 +} + +type S13 struct { + S8 +} + +type Ambig struct { + // Given "hello", the first match should win. + First int `json:"HELLO"` + Second int `json:"Hello"` +} + +type XYZ struct { + X any + Y any + Z any +} + +type unexportedWithMethods struct{} + +func (unexportedWithMethods) F() {} + +type byteWithMarshalJSON byte + +func (b byteWithMarshalJSON) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"Z%.2x"`, byte(b))), nil +} + +func (b *byteWithMarshalJSON) UnmarshalJSON(data []byte) error { + if len(data) != 5 || data[0] != '"' || data[1] != 'Z' || data[4] != '"' { + return fmt.Errorf("bad quoted string") + } + i, err := strconv.ParseInt(string(data[2:4]), 16, 8) + if err != nil { + return fmt.Errorf("bad hex") + } + *b = byteWithMarshalJSON(i) + return nil +} + +type byteWithPtrMarshalJSON byte + +func (b *byteWithPtrMarshalJSON) MarshalJSON() ([]byte, error) { + return byteWithMarshalJSON(*b).MarshalJSON() +} + +func (b *byteWithPtrMarshalJSON) UnmarshalJSON(data []byte) error { + return (*byteWithMarshalJSON)(b).UnmarshalJSON(data) +} + +type byteWithMarshalText byte + +func (b byteWithMarshalText) MarshalText() ([]byte, error) { + return []byte(fmt.Sprintf(`Z%.2x`, byte(b))), nil +} + +func (b *byteWithMarshalText) UnmarshalText(data []byte) error { + if len(data) != 3 || data[0] != 'Z' { + return fmt.Errorf("bad quoted string") + } + i, err := strconv.ParseInt(string(data[1:3]), 16, 8) + if err != nil { + return fmt.Errorf("bad hex") + } + *b = byteWithMarshalText(i) + return nil +} + +type byteWithPtrMarshalText byte + +func (b *byteWithPtrMarshalText) MarshalText() ([]byte, error) { + return byteWithMarshalText(*b).MarshalText() +} + +func (b *byteWithPtrMarshalText) UnmarshalText(data []byte) error { + return (*byteWithMarshalText)(b).UnmarshalText(data) +} + +type intWithMarshalJSON int + +func (b intWithMarshalJSON) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"Z%.2x"`, int(b))), nil +} + +func (b *intWithMarshalJSON) UnmarshalJSON(data []byte) error { + if len(data) != 5 || data[0] != '"' || data[1] != 'Z' || data[4] != '"' { + return fmt.Errorf("bad quoted string") + } + i, err := strconv.ParseInt(string(data[2:4]), 16, 8) + if err != nil { + return fmt.Errorf("bad hex") + } + *b = intWithMarshalJSON(i) + return nil +} + +type intWithPtrMarshalJSON int + +func (b *intWithPtrMarshalJSON) MarshalJSON() ([]byte, error) { + return intWithMarshalJSON(*b).MarshalJSON() +} + +func (b *intWithPtrMarshalJSON) UnmarshalJSON(data []byte) error { + return (*intWithMarshalJSON)(b).UnmarshalJSON(data) +} + +type intWithMarshalText int + +func (b intWithMarshalText) MarshalText() ([]byte, error) { + return []byte(fmt.Sprintf(`Z%.2x`, int(b))), nil +} + +func (b *intWithMarshalText) UnmarshalText(data []byte) error { + if len(data) != 3 || data[0] != 'Z' { + return fmt.Errorf("bad quoted string") + } + i, err := strconv.ParseInt(string(data[1:3]), 16, 8) + if err != nil { + return fmt.Errorf("bad hex") + } + *b = intWithMarshalText(i) + return nil +} + +type intWithPtrMarshalText int + +func (b *intWithPtrMarshalText) MarshalText() ([]byte, error) { + return intWithMarshalText(*b).MarshalText() +} + +func (b *intWithPtrMarshalText) UnmarshalText(data []byte) error { + return (*intWithMarshalText)(b).UnmarshalText(data) +} + +type mapStringToStringData struct { + Data map[string]string `json:"data"` +} + +type unmarshalTest struct { + in string + ptr any // new(type) + out any + err error + useNumber bool + golden bool + disallowUnknownFields bool +} + +type B struct { + B bool `json:",string"` +} + +type DoublePtr struct { + I **int + J **int +} + +var unmarshalTests = []unmarshalTest{ + // basic types + {in: `true`, ptr: new(bool), out: true}, + {in: `1`, ptr: new(int), out: 1}, + {in: `1.2`, ptr: new(float64), out: 1.2}, + {in: `-5`, ptr: new(int16), out: int16(-5)}, + {in: `2`, ptr: new(Number), out: Number("2"), useNumber: true}, + {in: `2`, ptr: new(Number), out: Number("2")}, + {in: `2`, ptr: new(any), out: float64(2.0)}, + {in: `2`, ptr: new(any), out: Number("2"), useNumber: true}, + {in: `"a\u1234"`, ptr: new(string), out: "a\u1234"}, + {in: `"http:\/\/"`, ptr: new(string), out: "http://"}, + {in: `"g-clef: \uD834\uDD1E"`, ptr: new(string), out: "g-clef: \U0001D11E"}, + {in: `"invalid: \uD834x\uDD1E"`, ptr: new(string), out: "invalid: \uFFFDx\uFFFD"}, + {in: "null", ptr: new(any), out: nil}, + {in: `{"X": [1,2,3], "Y": 4}`, ptr: new(T), out: T{Y: 4}, err: &UnmarshalTypeError{"array", reflect.TypeOf(""), 7, "T", "X"}}, + {in: `{"X": 23}`, ptr: new(T), out: T{}, err: &UnmarshalTypeError{"number", reflect.TypeOf(""), 8, "T", "X"}}, {in: `{"x": 1}`, ptr: new(tx), out: tx{}}, + {in: `{"x": 1}`, ptr: new(tx), out: tx{}}, + {in: `{"x": 1}`, ptr: new(tx), err: fmt.Errorf("json: unknown field \"x\""), disallowUnknownFields: true}, + {in: `{"S": 23}`, ptr: new(W), out: W{}, err: &UnmarshalTypeError{"number", reflect.TypeOf(SS("")), 0, "W", "S"}}, + {in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: float64(1), F2: int32(2), F3: Number("3")}}, + {in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: Number("1"), F2: int32(2), F3: Number("3")}, useNumber: true}, + {in: `{"k1":1,"k2":"s","k3":[1,2.0,3e-3],"k4":{"kk1":"s","kk2":2}}`, ptr: new(any), out: ifaceNumAsFloat64}, + {in: `{"k1":1,"k2":"s","k3":[1,2.0,3e-3],"k4":{"kk1":"s","kk2":2}}`, ptr: new(any), out: ifaceNumAsNumber, useNumber: true}, + + // raw values with whitespace + {in: "\n true ", ptr: new(bool), out: true}, + {in: "\t 1 ", ptr: new(int), out: 1}, + {in: "\r 1.2 ", ptr: new(float64), out: 1.2}, + {in: "\t -5 \n", ptr: new(int16), out: int16(-5)}, + {in: "\t \"a\\u1234\" \n", ptr: new(string), out: "a\u1234"}, + + // Z has a "-" tag. + {in: `{"Y": 1, "Z": 2}`, ptr: new(T), out: T{Y: 1}}, + {in: `{"Y": 1, "Z": 2}`, ptr: new(T), err: fmt.Errorf("json: unknown field \"Z\""), disallowUnknownFields: true}, + + {in: `{"alpha": "abc", "alphabet": "xyz"}`, ptr: new(U), out: U{Alphabet: "abc"}}, + {in: `{"alpha": "abc", "alphabet": "xyz"}`, ptr: new(U), err: fmt.Errorf("json: unknown field \"alphabet\""), disallowUnknownFields: true}, + {in: `{"alpha": "abc"}`, ptr: new(U), out: U{Alphabet: "abc"}}, + {in: `{"alphabet": "xyz"}`, ptr: new(U), out: U{}}, + {in: `{"alphabet": "xyz"}`, ptr: new(U), err: fmt.Errorf("json: unknown field \"alphabet\""), disallowUnknownFields: true}, + + // syntax errors + {in: `{"X": "foo", "Y"}`, err: &SyntaxError{"invalid character '}' after object key", 17}}, + {in: `[1, 2, 3+]`, err: &SyntaxError{"invalid character '+' after array element", 9}}, + {in: `{"X":12x}`, err: &SyntaxError{"invalid character 'x' after object key:value pair", 8}, useNumber: true}, + {in: `[2, 3`, err: &SyntaxError{msg: "unexpected end of JSON input", Offset: 5}}, + {in: `{"F3": -}`, ptr: new(V), out: V{F3: Number("-")}, err: &SyntaxError{msg: "invalid character '}' in numeric literal", Offset: 9}}, + + // raw value errors + {in: "\x01 42", err: &SyntaxError{"invalid character '\\x01' looking for beginning of value", 1}}, + {in: " 42 \x01", err: &SyntaxError{"invalid character '\\x01' after top-level value", 5}}, + {in: "\x01 true", err: &SyntaxError{"invalid character '\\x01' looking for beginning of value", 1}}, + {in: " false \x01", err: &SyntaxError{"invalid character '\\x01' after top-level value", 8}}, + {in: "\x01 1.2", err: &SyntaxError{"invalid character '\\x01' looking for beginning of value", 1}}, + {in: " 3.4 \x01", err: &SyntaxError{"invalid character '\\x01' after top-level value", 6}}, + {in: "\x01 \"string\"", err: &SyntaxError{"invalid character '\\x01' looking for beginning of value", 1}}, + {in: " \"string\" \x01", err: &SyntaxError{"invalid character '\\x01' after top-level value", 11}}, + + // array tests + {in: `[1, 2, 3]`, ptr: new([3]int), out: [3]int{1, 2, 3}}, + {in: `[1, 2, 3]`, ptr: new([1]int), out: [1]int{1}}, + {in: `[1, 2, 3]`, ptr: new([5]int), out: [5]int{1, 2, 3, 0, 0}}, + {in: `[1, 2, 3]`, ptr: new(MustNotUnmarshalJSON), err: errors.New("MustNotUnmarshalJSON was used")}, + + // empty array to interface test + {in: `[]`, ptr: new([]any), out: []any{}}, + {in: `null`, ptr: new([]any), out: []any(nil)}, + {in: `{"T":[]}`, ptr: new(map[string]any), out: map[string]any{"T": []any{}}}, + {in: `{"T":null}`, ptr: new(map[string]any), out: map[string]any{"T": any(nil)}}, + + // composite tests + {in: allValueIndent, ptr: new(All), out: allValue}, + {in: allValueCompact, ptr: new(All), out: allValue}, + {in: allValueIndent, ptr: new(*All), out: &allValue}, + {in: allValueCompact, ptr: new(*All), out: &allValue}, + {in: pallValueIndent, ptr: new(All), out: pallValue}, + {in: pallValueCompact, ptr: new(All), out: pallValue}, + {in: pallValueIndent, ptr: new(*All), out: &pallValue}, + {in: pallValueCompact, ptr: new(*All), out: &pallValue}, + + // unmarshal interface test + {in: `{"T":false}`, ptr: new(unmarshaler), out: umtrue}, // use "false" so test will fail if custom unmarshaler is not called + {in: `{"T":false}`, ptr: new(*unmarshaler), out: &umtrue}, + {in: `[{"T":false}]`, ptr: new([]unmarshaler), out: umslice}, + {in: `[{"T":false}]`, ptr: new(*[]unmarshaler), out: &umslice}, + {in: `{"M":{"T":"x:y"}}`, ptr: new(ustruct), out: umstruct}, + + // UnmarshalText interface test + {in: `"x:y"`, ptr: new(unmarshalerText), out: umtrueXY}, + {in: `"x:y"`, ptr: new(*unmarshalerText), out: &umtrueXY}, + {in: `["x:y"]`, ptr: new([]unmarshalerText), out: umsliceXY}, + {in: `["x:y"]`, ptr: new(*[]unmarshalerText), out: &umsliceXY}, + {in: `{"M":"x:y"}`, ptr: new(ustructText), out: umstructXY}, + + // integer-keyed map test + { + in: `{"-1":"a","0":"b","1":"c"}`, + ptr: new(map[int]string), + out: map[int]string{-1: "a", 0: "b", 1: "c"}, + }, + { + in: `{"0":"a","10":"c","9":"b"}`, + ptr: new(map[u8]string), + out: map[u8]string{0: "a", 9: "b", 10: "c"}, + }, + { + in: `{"-9223372036854775808":"min","9223372036854775807":"max"}`, + ptr: new(map[int64]string), + out: map[int64]string{math.MinInt64: "min", math.MaxInt64: "max"}, + }, + { + in: `{"18446744073709551615":"max"}`, + ptr: new(map[uint64]string), + out: map[uint64]string{math.MaxUint64: "max"}, + }, + { + in: `{"0":false,"10":true}`, + ptr: new(map[uintptr]bool), + out: map[uintptr]bool{0: false, 10: true}, + }, + + // Check that MarshalText and UnmarshalText take precedence + // over default integer handling in map keys. + { + in: `{"u2":4}`, + ptr: new(map[u8marshal]int), + out: map[u8marshal]int{2: 4}, + }, + { + in: `{"2":4}`, + ptr: new(map[u8marshal]int), + err: errMissingU8Prefix, + }, + + // integer-keyed map errors + { + in: `{"abc":"abc"}`, + ptr: new(map[int]string), + err: &UnmarshalTypeError{Value: "number abc", Type: reflect.TypeOf(0), Offset: 2}, + }, + { + in: `{"256":"abc"}`, + ptr: new(map[uint8]string), + err: &UnmarshalTypeError{Value: "number 256", Type: reflect.TypeOf(uint8(0)), Offset: 2}, + }, + { + in: `{"128":"abc"}`, + ptr: new(map[int8]string), + err: &UnmarshalTypeError{Value: "number 128", Type: reflect.TypeOf(int8(0)), Offset: 2}, + }, + { + in: `{"-1":"abc"}`, + ptr: new(map[uint8]string), + err: &UnmarshalTypeError{Value: "number -1", Type: reflect.TypeOf(uint8(0)), Offset: 2}, + }, + { + in: `{"F":{"a":2,"3":4}}`, + ptr: new(map[string]map[int]int), + err: &UnmarshalTypeError{Value: "number a", Type: reflect.TypeOf(int(0)), Offset: 7}, + }, + { + in: `{"F":{"a":2,"3":4}}`, + ptr: new(map[string]map[uint]int), + err: &UnmarshalTypeError{Value: "number a", Type: reflect.TypeOf(uint(0)), Offset: 7}, + }, + + // Map keys can be encoding.TextUnmarshalers. + {in: `{"x:y":true}`, ptr: new(map[unmarshalerText]bool), out: ummapXY}, + // If multiple values for the same key exists, only the most recent value is used. + {in: `{"x:y":false,"x:y":true}`, ptr: new(map[unmarshalerText]bool), out: ummapXY}, + + { + in: `{ + "Level0": 1, + "Level1b": 2, + "Level1c": 3, + "x": 4, + "Level1a": 5, + "LEVEL1B": 6, + "e": { + "Level1a": 8, + "Level1b": 9, + "Level1c": 10, + "Level1d": 11, + "x": 12 + }, + "Loop1": 13, + "Loop2": 14, + "X": 15, + "Y": 16, + "Z": 17, + "Q": 18 + }`, + ptr: new(Top), + out: Top{ + Level0: 1, + Embed0: Embed0{ + Level1b: 2, + Level1c: 3, + }, + Embed0a: &Embed0a{ + Level1a: 5, + Level1b: 6, + }, + Embed0b: &Embed0b{ + Level1a: 8, + Level1b: 9, + Level1c: 10, + Level1d: 11, + Level1e: 12, + }, + Loop: Loop{ + Loop1: 13, + Loop2: 14, + }, + Embed0p: Embed0p{ + Point: image.Point{X: 15, Y: 16}, + }, + Embed0q: Embed0q{ + Point: Point{Z: 17}, + }, + embed: embed{ + Q: 18, + }, + }, + }, + { + in: `{"hello": 1}`, + ptr: new(Ambig), + out: Ambig{First: 1}, + }, + + { + in: `{"X": 1,"Y":2}`, + ptr: new(S5), + out: S5{S8: S8{S9: S9{Y: 2}}}, + }, + { + in: `{"X": 1,"Y":2}`, + ptr: new(S5), + err: fmt.Errorf("json: unknown field \"X\""), + disallowUnknownFields: true, + }, + { + in: `{"X": 1,"Y":2}`, + ptr: new(S10), + out: S10{S13: S13{S8: S8{S9: S9{Y: 2}}}}, + }, + { + in: `{"X": 1,"Y":2}`, + ptr: new(S10), + err: fmt.Errorf("json: unknown field \"X\""), + disallowUnknownFields: true, + }, + { + in: `{"I": 0, "I": null, "J": null}`, + ptr: new(DoublePtr), + out: DoublePtr{I: nil, J: nil}, + }, + + // invalid UTF-8 is coerced to valid UTF-8. + { + in: "\"hello\xffworld\"", + ptr: new(string), + out: "hello\ufffdworld", + }, + { + in: "\"hello\xc2\xc2world\"", + ptr: new(string), + out: "hello\ufffd\ufffdworld", + }, + { + in: "\"hello\xc2\xffworld\"", + ptr: new(string), + out: "hello\ufffd\ufffdworld", + }, + { + in: "\"hello\\ud800world\"", + ptr: new(string), + out: "hello\ufffdworld", + }, + { + in: "\"hello\\ud800\\ud800world\"", + ptr: new(string), + out: "hello\ufffd\ufffdworld", + }, + { + in: "\"hello\\ud800\\ud800world\"", + ptr: new(string), + out: "hello\ufffd\ufffdworld", + }, + { + in: "\"hello\xed\xa0\x80\xed\xb0\x80world\"", + ptr: new(string), + out: "hello\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdworld", + }, + + // Used to be issue 8305, but time.Time implements encoding.TextUnmarshaler so this works now. + { + in: `{"2009-11-10T23:00:00Z": "hello world"}`, + ptr: new(map[time.Time]string), + out: map[time.Time]string{time.Date(2009, 11, 10, 23, 0, 0, 0, time.UTC): "hello world"}, + }, + + // issue 8305 + { + in: `{"2009-11-10T23:00:00Z": "hello world"}`, + ptr: new(map[Point]string), + err: &UnmarshalTypeError{Value: "object", Type: reflect.TypeOf(map[Point]string{}), Offset: 1}, + }, + { + in: `{"asdf": "hello world"}`, + ptr: new(map[unmarshaler]string), + err: &UnmarshalTypeError{Value: "object", Type: reflect.TypeOf(map[unmarshaler]string{}), Offset: 1}, + }, + + // related to issue 13783. + // Go 1.7 changed marshaling a slice of typed byte to use the methods on the byte type, + // similar to marshaling a slice of typed int. + // These tests check that, assuming the byte type also has valid decoding methods, + // either the old base64 string encoding or the new per-element encoding can be + // successfully unmarshaled. The custom unmarshalers were accessible in earlier + // versions of Go, even though the custom marshaler was not. + { + in: `"AQID"`, + ptr: new([]byteWithMarshalJSON), + out: []byteWithMarshalJSON{1, 2, 3}, + }, + { + in: `["Z01","Z02","Z03"]`, + ptr: new([]byteWithMarshalJSON), + out: []byteWithMarshalJSON{1, 2, 3}, + golden: true, + }, + { + in: `"AQID"`, + ptr: new([]byteWithMarshalText), + out: []byteWithMarshalText{1, 2, 3}, + }, + { + in: `["Z01","Z02","Z03"]`, + ptr: new([]byteWithMarshalText), + out: []byteWithMarshalText{1, 2, 3}, + golden: true, + }, + { + in: `"AQID"`, + ptr: new([]byteWithPtrMarshalJSON), + out: []byteWithPtrMarshalJSON{1, 2, 3}, + }, + { + in: `["Z01","Z02","Z03"]`, + ptr: new([]byteWithPtrMarshalJSON), + out: []byteWithPtrMarshalJSON{1, 2, 3}, + golden: true, + }, + { + in: `"AQID"`, + ptr: new([]byteWithPtrMarshalText), + out: []byteWithPtrMarshalText{1, 2, 3}, + }, + { + in: `["Z01","Z02","Z03"]`, + ptr: new([]byteWithPtrMarshalText), + out: []byteWithPtrMarshalText{1, 2, 3}, + golden: true, + }, + + // ints work with the marshaler but not the base64 []byte case + { + in: `["Z01","Z02","Z03"]`, + ptr: new([]intWithMarshalJSON), + out: []intWithMarshalJSON{1, 2, 3}, + golden: true, + }, + { + in: `["Z01","Z02","Z03"]`, + ptr: new([]intWithMarshalText), + out: []intWithMarshalText{1, 2, 3}, + golden: true, + }, + { + in: `["Z01","Z02","Z03"]`, + ptr: new([]intWithPtrMarshalJSON), + out: []intWithPtrMarshalJSON{1, 2, 3}, + golden: true, + }, + { + in: `["Z01","Z02","Z03"]`, + ptr: new([]intWithPtrMarshalText), + out: []intWithPtrMarshalText{1, 2, 3}, + golden: true, + }, + + {in: `0.000001`, ptr: new(float64), out: 0.000001, golden: true}, + {in: `1e-7`, ptr: new(float64), out: 1e-7, golden: true}, + {in: `100000000000000000000`, ptr: new(float64), out: 100000000000000000000.0, golden: true}, + {in: `1e+21`, ptr: new(float64), out: 1e21, golden: true}, + {in: `-0.000001`, ptr: new(float64), out: -0.000001, golden: true}, + {in: `-1e-7`, ptr: new(float64), out: -1e-7, golden: true}, + {in: `-100000000000000000000`, ptr: new(float64), out: -100000000000000000000.0, golden: true}, + {in: `-1e+21`, ptr: new(float64), out: -1e21, golden: true}, + {in: `999999999999999900000`, ptr: new(float64), out: 999999999999999900000.0, golden: true}, + {in: `9007199254740992`, ptr: new(float64), out: 9007199254740992.0, golden: true}, + {in: `9007199254740993`, ptr: new(float64), out: 9007199254740992.0, golden: false}, + + { + in: `{"V": {"F2": "hello"}}`, + ptr: new(VOuter), + err: &UnmarshalTypeError{ + Value: "string", + Struct: "V", + Field: "V.F2", + Type: reflect.TypeOf(int32(0)), + Offset: 20, + }, + }, + { + in: `{"V": {"F4": {}, "F2": "hello"}}`, + ptr: new(VOuter), + err: &UnmarshalTypeError{ + Value: "string", + Struct: "V", + Field: "V.F2", + Type: reflect.TypeOf(int32(0)), + Offset: 30, + }, + }, + + // issue 15146. + // invalid inputs in wrongStringTests below. + {in: `{"B":"true"}`, ptr: new(B), out: B{true}, golden: true}, + {in: `{"B":"false"}`, ptr: new(B), out: B{false}, golden: true}, + {in: `{"B": "maybe"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "maybe" into bool`)}, + {in: `{"B": "tru"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "tru" into bool`)}, + {in: `{"B": "False"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "False" into bool`)}, + {in: `{"B": "null"}`, ptr: new(B), out: B{false}}, + {in: `{"B": "nul"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "nul" into bool`)}, + {in: `{"B": [2, 3]}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal unquoted value into bool`)}, + + // additional tests for disallowUnknownFields + { + in: `{ + "Level0": 1, + "Level1b": 2, + "Level1c": 3, + "x": 4, + "Level1a": 5, + "LEVEL1B": 6, + "e": { + "Level1a": 8, + "Level1b": 9, + "Level1c": 10, + "Level1d": 11, + "x": 12 + }, + "Loop1": 13, + "Loop2": 14, + "X": 15, + "Y": 16, + "Z": 17, + "Q": 18, + "extra": true + }`, + ptr: new(Top), + err: fmt.Errorf("json: unknown field \"extra\""), + disallowUnknownFields: true, + }, + { + in: `{ + "Level0": 1, + "Level1b": 2, + "Level1c": 3, + "x": 4, + "Level1a": 5, + "LEVEL1B": 6, + "e": { + "Level1a": 8, + "Level1b": 9, + "Level1c": 10, + "Level1d": 11, + "x": 12, + "extra": null + }, + "Loop1": 13, + "Loop2": 14, + "X": 15, + "Y": 16, + "Z": 17, + "Q": 18 + }`, + ptr: new(Top), + err: fmt.Errorf("json: unknown field \"extra\""), + disallowUnknownFields: true, + }, + // issue 26444 + // UnmarshalTypeError without field & struct values + { + in: `{"data":{"test1": "bob", "test2": 123}}`, + ptr: new(mapStringToStringData), + err: &UnmarshalTypeError{Value: "number", Type: reflect.TypeOf(""), Offset: 37, Struct: "mapStringToStringData", Field: "data"}, + }, + { + in: `{"data":{"test1": 123, "test2": "bob"}}`, + ptr: new(mapStringToStringData), + err: &UnmarshalTypeError{Value: "number", Type: reflect.TypeOf(""), Offset: 21, Struct: "mapStringToStringData", Field: "data"}, + }, + + // trying to decode JSON arrays or objects via TextUnmarshaler + { + in: `[1, 2, 3]`, + ptr: new(MustNotUnmarshalText), + err: &UnmarshalTypeError{Value: "array", Type: reflect.TypeOf(&MustNotUnmarshalText{}), Offset: 1}, + }, + { + in: `{"foo": "bar"}`, + ptr: new(MustNotUnmarshalText), + err: &UnmarshalTypeError{Value: "object", Type: reflect.TypeOf(&MustNotUnmarshalText{}), Offset: 1}, + }, + // #22369 + { + in: `{"PP": {"T": {"Y": "bad-type"}}}`, + ptr: new(P), + err: &UnmarshalTypeError{ + Value: "string", + Struct: "T", + Field: "PP.T.Y", + Type: reflect.TypeOf(int(0)), + Offset: 29, + }, + }, + { + in: `{"Ts": [{"Y": 1}, {"Y": 2}, {"Y": "bad-type"}]}`, + ptr: new(PP), + err: &UnmarshalTypeError{ + Value: "string", + Struct: "T", + Field: "Ts.Y", + Type: reflect.TypeOf(int(0)), + Offset: 29, + }, + }, + // #14702 + { + in: `invalid`, + ptr: new(Number), + err: &SyntaxError{ + msg: "invalid character 'i' looking for beginning of value", + Offset: 1, + }, + }, + { + in: `"invalid"`, + ptr: new(Number), + err: fmt.Errorf("json: invalid number literal, trying to unmarshal %q into Number", `"invalid"`), + }, + { + in: `{"A":"invalid"}`, + ptr: new(struct{ A Number }), + err: fmt.Errorf("json: invalid number literal, trying to unmarshal %q into Number", `"invalid"`), + }, + { + in: `{"A":"invalid"}`, + ptr: new(struct { + A Number `json:",string"` + }), + err: fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into json.Number", `invalid`), + }, + { + in: `{"A":"invalid"}`, + ptr: new(map[string]Number), + err: fmt.Errorf("json: invalid number literal, trying to unmarshal %q into Number", `"invalid"`), + }, +} + +func TestMarshal(t *testing.T) { + b, err := Marshal(allValue) + if err != nil { + t.Fatalf("Marshal allValue: %v", err) + } + if string(b) != allValueCompact { + t.Errorf("Marshal allValueCompact") + diff(t, b, []byte(allValueCompact)) + return + } + + b, err = Marshal(pallValue) + if err != nil { + t.Fatalf("Marshal pallValue: %v", err) + } + if string(b) != pallValueCompact { + t.Errorf("Marshal pallValueCompact") + diff(t, b, []byte(pallValueCompact)) + return + } +} + +var badUTF8 = []struct { + in, out string +}{ + {"hello\xffworld", `"hello\ufffdworld"`}, + {"", `""`}, + {"\xff", `"\ufffd"`}, + {"\xff\xff", `"\ufffd\ufffd"`}, + {"a\xffb", `"a\ufffdb"`}, + {"\xe6\x97\xa5\xe6\x9c\xac\xff\xaa\x9e", `"日本\ufffd\ufffd\ufffd"`}, +} + +func TestMarshalBadUTF8(t *testing.T) { + for _, tt := range badUTF8 { + b, err := Marshal(tt.in) + if string(b) != tt.out || err != nil { + t.Errorf("Marshal(%q) = %#q, %v, want %#q, nil", tt.in, b, err, tt.out) + } + } +} + +func TestMarshalNumberZeroVal(t *testing.T) { + var n Number + out, err := Marshal(n) + if err != nil { + t.Fatal(err) + } + outStr := string(out) + if outStr != "0" { + t.Fatalf("Invalid zero val for Number: %q", outStr) + } +} + +func TestMarshalEmbeds(t *testing.T) { + t.Skip() // TODO + top := &Top{ + Level0: 1, + Embed0: Embed0{ + Level1b: 2, + Level1c: 3, + }, + Embed0a: &Embed0a{ + Level1a: 5, + Level1b: 6, + }, + Embed0b: &Embed0b{ + Level1a: 8, + Level1b: 9, + Level1c: 10, + Level1d: 11, + Level1e: 12, + }, + Loop: Loop{ + Loop1: 13, + Loop2: 14, + }, + Embed0p: Embed0p{ + Point: image.Point{X: 15, Y: 16}, + }, + Embed0q: Embed0q{ + Point: Point{Z: 17}, + }, + embed: embed{ + Q: 18, + }, + } + b, err := Marshal(top) + if err != nil { + t.Fatal(err) + } + want := "{\"Level0\":1,\"Level1b\":2,\"Level1c\":3,\"Level1a\":5,\"LEVEL1B\":6,\"e\":{\"Level1a\":8,\"Level1b\":9,\"Level1c\":10,\"Level1d\":11,\"x\":12},\"Loop1\":13,\"Loop2\":14,\"X\":15,\"Y\":16,\"Z\":17,\"Q\":18}" + if string(b) != want { + t.Errorf("Wrong marshal result.\n got: %q\nwant: %q", b, want) + } +} + +func equalError(a, b error) bool { + if a == nil { + return b == nil + } + if b == nil { + return a == nil + } + return true // a.Error() == b.Error() // MODIFIED +} + +func TestUnmarshal(t *testing.T) { + t.Skip() // TODO + for i, tt := range unmarshalTests { + scan := &ReEncoder{ + Out: io.Discard, + } + in := []byte(tt.in) + if _, err := scan.Write(in); err != nil { + if !equalError(err, tt.err) { + t.Errorf("#%d: checkValid: %#v\n\n%s", i, err, tt.in) + continue + } + } + if tt.ptr == nil { + continue + } + + typ := reflect.TypeOf(tt.ptr) + if typ.Kind() != reflect.Pointer { + t.Errorf("#%d: unmarshalTest.ptr %T is not a pointer type", i, tt.ptr) + continue + } + typ = typ.Elem() + + // v = new(right-type) + v := reflect.New(typ) + + if !reflect.DeepEqual(tt.ptr, v.Interface()) { + // There's no reason for ptr to point to non-zero data, + // as we decode into new(right-type), so the data is + // discarded. + // This can easily mean tests that silently don't test + // what they should. To test decoding into existing + // data, see TestPrefilled. + t.Errorf("#%d: unmarshalTest.ptr %#v is not a pointer to a zero value", i, tt.ptr) + continue + } + + dec := NewDecoder(bytes.NewReader(in)) + if tt.useNumber { + dec.UseNumber() + } + if tt.disallowUnknownFields { + dec.DisallowUnknownFields() + } + if err := dec.Decode(v.Interface()); !equalError(err, tt.err) { + t.Errorf("#%d: %v, want %v", i, err, tt.err) + continue + } else if err != nil { + continue + } + if !reflect.DeepEqual(v.Elem().Interface(), tt.out) { + t.Errorf("#%d: mismatch\nhave: %#+v\nwant: %#+v\n\n%s", i, v.Elem().Interface(), tt.out, tt.in) + data, _ := Marshal(v.Elem().Interface()) + println(string(data)) + data, _ = Marshal(tt.out) + println(string(data)) + continue + } + + // Check round trip also decodes correctly. + if tt.err == nil { + enc, err := Marshal(v.Interface()) + if err != nil { + t.Errorf("#%d: error re-marshaling: %v", i, err) + continue + } + if tt.golden && !bytes.Equal(enc, in) { + t.Errorf("#%d: remarshal mismatch:\nhave: %s\nwant: %s", i, enc, in) + } + vv := reflect.New(reflect.TypeOf(tt.ptr).Elem()) + dec = NewDecoder(bytes.NewReader(enc)) + if tt.useNumber { + dec.UseNumber() + } + if err := dec.Decode(vv.Interface()); err != nil { + t.Errorf("#%d: error re-unmarshaling %#q: %v", i, enc, err) + continue + } + if !reflect.DeepEqual(v.Elem().Interface(), vv.Elem().Interface()) { + t.Errorf("#%d: mismatch\nhave: %#+v\nwant: %#+v", i, v.Elem().Interface(), vv.Elem().Interface()) + t.Errorf(" In: %q", strings.Map(noSpace, string(in))) + t.Errorf("Marshal: %q", strings.Map(noSpace, string(enc))) + continue + } + } + } +} + +func TestUnmarshalMarshal(t *testing.T) { + initBig() + var v any + if err := Unmarshal(jsonBig, &v); err != nil { + t.Fatalf("Unmarshal: %v", err) + } + b, err := Marshal(v) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + if !bytes.Equal(jsonBig, b) { + t.Errorf("Marshal jsonBig") + diff(t, b, jsonBig) + return + } +} + +var numberTests = []struct { + in string + i int64 + intErr string + f float64 + floatErr string +}{ + {in: "-1.23e1", intErr: "strconv.ParseInt: parsing \"-1.23e1\": invalid syntax", f: -1.23e1}, + {in: "-12", i: -12, f: -12.0}, + {in: "1e1000", intErr: "strconv.ParseInt: parsing \"1e1000\": invalid syntax", floatErr: "strconv.ParseFloat: parsing \"1e1000\": value out of range"}, +} + +// Independent of Decode, basic coverage of the accessors in Number +func TestNumberAccessors(t *testing.T) { + for _, tt := range numberTests { + n := Number(tt.in) + if s := n.String(); s != tt.in { + t.Errorf("Number(%q).String() is %q", tt.in, s) + } + if i, err := n.Int64(); err == nil && tt.intErr == "" && i != tt.i { + t.Errorf("Number(%q).Int64() is %d", tt.in, i) + } else if (err == nil && tt.intErr != "") || (err != nil && err.Error() != tt.intErr) { + t.Errorf("Number(%q).Int64() wanted error %q but got: %v", tt.in, tt.intErr, err) + } + if f, err := n.Float64(); err == nil && tt.floatErr == "" && f != tt.f { + t.Errorf("Number(%q).Float64() is %g", tt.in, f) + } else if (err == nil && tt.floatErr != "") || (err != nil && err.Error() != tt.floatErr) { + t.Errorf("Number(%q).Float64() wanted error %q but got: %v", tt.in, tt.floatErr, err) + } + } +} + +func TestLargeByteSlice(t *testing.T) { + s0 := make([]byte, 2000) + for i := range s0 { + s0[i] = byte(i) + } + b, err := Marshal(s0) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + var s1 []byte + if err := Unmarshal(b, &s1); err != nil { + t.Fatalf("Unmarshal: %v", err) + } + if !bytes.Equal(s0, s1) { + t.Errorf("Marshal large byte slice") + diff(t, s0, s1) + } +} + +type Xint struct { + X int +} + +func TestUnmarshalInterface(t *testing.T) { + var xint Xint + var i any = &xint + if err := Unmarshal([]byte(`{"X":1}`), &i); err != nil { + t.Fatalf("Unmarshal: %v", err) + } + if xint.X != 1 { + t.Fatalf("Did not write to xint") + } +} + +func TestUnmarshalPtrPtr(t *testing.T) { + var xint Xint + pxint := &xint + if err := Unmarshal([]byte(`{"X":1}`), &pxint); err != nil { + t.Fatalf("Unmarshal: %v", err) + } + if xint.X != 1 { + t.Fatalf("Did not write to xint") + } +} + +func TestEscape(t *testing.T) { + const input = `"foobar"` + " [\u2028 \u2029]" + const expected = `"\"foobar\"\u003chtml\u003e [\u2028 \u2029]"` + b, err := Marshal(input) + if err != nil { + t.Fatalf("Marshal error: %v", err) + } + if s := string(b); s != expected { + t.Errorf("Encoding of [%s]:\n got [%s]\nwant [%s]", input, s, expected) + } +} + +// WrongString is a struct that's misusing the ,string modifier. +type WrongString struct { + Message string `json:"result,string"` +} + +type wrongStringTest struct { + in, err string +} + +var wrongStringTests = []wrongStringTest{ + {`{"result":"x"}`, `json: invalid use of ,string struct tag, trying to unmarshal "x" into string`}, + {`{"result":"foo"}`, `json: invalid use of ,string struct tag, trying to unmarshal "foo" into string`}, + {`{"result":"123"}`, `json: invalid use of ,string struct tag, trying to unmarshal "123" into string`}, + {`{"result":123}`, `json: invalid use of ,string struct tag, trying to unmarshal unquoted value into string`}, + {`{"result":"\""}`, `json: invalid use of ,string struct tag, trying to unmarshal "\"" into string`}, + {`{"result":"\"foo"}`, `json: invalid use of ,string struct tag, trying to unmarshal "\"foo" into string`}, +} + +// If people misuse the ,string modifier, the error message should be +// helpful, telling the user that they're doing it wrong. +func TestErrorMessageFromMisusedString(t *testing.T) { + for n, tt := range wrongStringTests { + r := strings.NewReader(tt.in) + var s WrongString + err := NewDecoder(r).Decode(&s) + got := fmt.Sprintf("%v", err) + if err == nil { // if got != tt.err { // MODIFIED + t.Errorf("%d. got err = %q, want %q", n, got, tt.err) + } + } +} + +func noSpace(c rune) rune { + if isSpace(byte(c)) { //only used for ascii + return -1 + } + return c +} + +type All struct { + Bool bool + Int int + Int8 int8 + Int16 int16 + Int32 int32 + Int64 int64 + Uint uint + Uint8 uint8 + Uint16 uint16 + Uint32 uint32 + Uint64 uint64 + Uintptr uintptr + Float32 float32 + Float64 float64 + + Foo string `json:"bar"` + Foo2 string `json:"bar2,dummyopt"` + + IntStr int64 `json:",string"` + UintptrStr uintptr `json:",string"` + + PBool *bool + PInt *int + PInt8 *int8 + PInt16 *int16 + PInt32 *int32 + PInt64 *int64 + PUint *uint + PUint8 *uint8 + PUint16 *uint16 + PUint32 *uint32 + PUint64 *uint64 + PUintptr *uintptr + PFloat32 *float32 + PFloat64 *float64 + + String string + PString *string + + Map map[string]Small + MapP map[string]*Small + PMap *map[string]Small + PMapP *map[string]*Small + + EmptyMap map[string]Small + NilMap map[string]Small + + Slice []Small + SliceP []*Small + PSlice *[]Small + PSliceP *[]*Small + + EmptySlice []Small + NilSlice []Small + + StringSlice []string + ByteSlice []byte + + Small Small + PSmall *Small + PPSmall **Small + + Interface any + PInterface *any + + unexported int +} + +type Small struct { + Tag string +} + +var allValue = All{ + Bool: true, + Int: 2, + Int8: 3, + Int16: 4, + Int32: 5, + Int64: 6, + Uint: 7, + Uint8: 8, + Uint16: 9, + Uint32: 10, + Uint64: 11, + Uintptr: 12, + Float32: 14.1, + Float64: 15.1, + Foo: "foo", + Foo2: "foo2", + IntStr: 42, + UintptrStr: 44, + String: "16", + Map: map[string]Small{ + "17": {Tag: "tag17"}, + "18": {Tag: "tag18"}, + }, + MapP: map[string]*Small{ + "19": {Tag: "tag19"}, + "20": nil, + }, + EmptyMap: map[string]Small{}, + Slice: []Small{{Tag: "tag20"}, {Tag: "tag21"}}, + SliceP: []*Small{{Tag: "tag22"}, nil, {Tag: "tag23"}}, + EmptySlice: []Small{}, + StringSlice: []string{"str24", "str25", "str26"}, + ByteSlice: []byte{27, 28, 29}, + Small: Small{Tag: "tag30"}, + PSmall: &Small{Tag: "tag31"}, + Interface: 5.2, +} + +var pallValue = All{ + PBool: &allValue.Bool, + PInt: &allValue.Int, + PInt8: &allValue.Int8, + PInt16: &allValue.Int16, + PInt32: &allValue.Int32, + PInt64: &allValue.Int64, + PUint: &allValue.Uint, + PUint8: &allValue.Uint8, + PUint16: &allValue.Uint16, + PUint32: &allValue.Uint32, + PUint64: &allValue.Uint64, + PUintptr: &allValue.Uintptr, + PFloat32: &allValue.Float32, + PFloat64: &allValue.Float64, + PString: &allValue.String, + PMap: &allValue.Map, + PMapP: &allValue.MapP, + PSlice: &allValue.Slice, + PSliceP: &allValue.SliceP, + PPSmall: &allValue.PSmall, + PInterface: &allValue.Interface, +} + +var allValueIndent = `{ + "Bool": true, + "Int": 2, + "Int8": 3, + "Int16": 4, + "Int32": 5, + "Int64": 6, + "Uint": 7, + "Uint8": 8, + "Uint16": 9, + "Uint32": 10, + "Uint64": 11, + "Uintptr": 12, + "Float32": 14.1, + "Float64": 15.1, + "bar": "foo", + "bar2": "foo2", + "IntStr": "42", + "UintptrStr": "44", + "PBool": null, + "PInt": null, + "PInt8": null, + "PInt16": null, + "PInt32": null, + "PInt64": null, + "PUint": null, + "PUint8": null, + "PUint16": null, + "PUint32": null, + "PUint64": null, + "PUintptr": null, + "PFloat32": null, + "PFloat64": null, + "String": "16", + "PString": null, + "Map": { + "17": { + "Tag": "tag17" + }, + "18": { + "Tag": "tag18" + } + }, + "MapP": { + "19": { + "Tag": "tag19" + }, + "20": null + }, + "PMap": null, + "PMapP": null, + "EmptyMap": {}, + "NilMap": null, + "Slice": [ + { + "Tag": "tag20" + }, + { + "Tag": "tag21" + } + ], + "SliceP": [ + { + "Tag": "tag22" + }, + null, + { + "Tag": "tag23" + } + ], + "PSlice": null, + "PSliceP": null, + "EmptySlice": [], + "NilSlice": null, + "StringSlice": [ + "str24", + "str25", + "str26" + ], + "ByteSlice": "Gxwd", + "Small": { + "Tag": "tag30" + }, + "PSmall": { + "Tag": "tag31" + }, + "PPSmall": null, + "Interface": 5.2, + "PInterface": null +}` + +var allValueCompact = strings.Map(noSpace, allValueIndent) + +var pallValueIndent = `{ + "Bool": false, + "Int": 0, + "Int8": 0, + "Int16": 0, + "Int32": 0, + "Int64": 0, + "Uint": 0, + "Uint8": 0, + "Uint16": 0, + "Uint32": 0, + "Uint64": 0, + "Uintptr": 0, + "Float32": 0, + "Float64": 0, + "bar": "", + "bar2": "", + "IntStr": "0", + "UintptrStr": "0", + "PBool": true, + "PInt": 2, + "PInt8": 3, + "PInt16": 4, + "PInt32": 5, + "PInt64": 6, + "PUint": 7, + "PUint8": 8, + "PUint16": 9, + "PUint32": 10, + "PUint64": 11, + "PUintptr": 12, + "PFloat32": 14.1, + "PFloat64": 15.1, + "String": "", + "PString": "16", + "Map": null, + "MapP": null, + "PMap": { + "17": { + "Tag": "tag17" + }, + "18": { + "Tag": "tag18" + } + }, + "PMapP": { + "19": { + "Tag": "tag19" + }, + "20": null + }, + "EmptyMap": null, + "NilMap": null, + "Slice": null, + "SliceP": null, + "PSlice": [ + { + "Tag": "tag20" + }, + { + "Tag": "tag21" + } + ], + "PSliceP": [ + { + "Tag": "tag22" + }, + null, + { + "Tag": "tag23" + } + ], + "EmptySlice": null, + "NilSlice": null, + "StringSlice": null, + "ByteSlice": null, + "Small": { + "Tag": "" + }, + "PSmall": null, + "PPSmall": { + "Tag": "tag31" + }, + "Interface": null, + "PInterface": 5.2 +}` + +var pallValueCompact = strings.Map(noSpace, pallValueIndent) + +func TestRefUnmarshal(t *testing.T) { + type S struct { + // Ref is defined in encode_test.go. + R0 Ref + R1 *Ref + R2 RefText + R3 *RefText + } + want := S{ + R0: 12, + R1: new(Ref), + R2: 13, + R3: new(RefText), + } + *want.R1 = 12 + *want.R3 = 13 + + var got S + if err := Unmarshal([]byte(`{"R0":"ref","R1":"ref","R2":"ref","R3":"ref"}`), &got); err != nil { + t.Fatalf("Unmarshal: %v", err) + } + if !reflect.DeepEqual(got, want) { + t.Errorf("got %+v, want %+v", got, want) + } +} + +// Test that the empty string doesn't panic decoding when ,string is specified +// Issue 3450 +func TestEmptyString(t *testing.T) { + type T2 struct { + Number1 int `json:",string"` + Number2 int `json:",string"` + } + data := `{"Number1":"1", "Number2":""}` + dec := NewDecoder(strings.NewReader(data)) + var t2 T2 + err := dec.Decode(&t2) + if err == nil { + t.Fatal("Decode: did not return error") + } + if t2.Number1 != 1 { + t.Fatal("Decode: did not set Number1") + } +} + +// Test that a null for ,string is not replaced with the previous quoted string (issue 7046). +// It should also not be an error (issue 2540, issue 8587). +func TestNullString(t *testing.T) { + type T struct { + A int `json:",string"` + B int `json:",string"` + C *int `json:",string"` + } + data := []byte(`{"A": "1", "B": null, "C": null}`) + var s T + s.B = 1 + s.C = new(int) + *s.C = 2 + err := Unmarshal(data, &s) + if err != nil { + t.Fatalf("Unmarshal: %v", err) + } + if s.B != 1 || s.C != nil { + t.Fatalf("after Unmarshal, s.B=%d, s.C=%p, want 1, nil", s.B, s.C) + } +} + +func intp(x int) *int { + p := new(int) + *p = x + return p +} + +func intpp(x *int) **int { + pp := new(*int) + *pp = x + return pp +} + +var interfaceSetTests = []struct { + pre any + json string + post any +}{ + {"foo", `"bar"`, "bar"}, + {"foo", `2`, 2.0}, + {"foo", `true`, true}, + {"foo", `null`, nil}, + + {nil, `null`, nil}, + {new(int), `null`, nil}, + {(*int)(nil), `null`, nil}, + {new(*int), `null`, new(*int)}, + {(**int)(nil), `null`, nil}, + {intp(1), `null`, nil}, + {intpp(nil), `null`, intpp(nil)}, + {intpp(intp(1)), `null`, intpp(nil)}, +} + +func TestInterfaceSet(t *testing.T) { + t.Skip() // TODO + for _, tt := range interfaceSetTests { + b := struct{ X any }{tt.pre} + blob := `{"X":` + tt.json + `}` + if err := Unmarshal([]byte(blob), &b); err != nil { + t.Errorf("Unmarshal %#q: %v", blob, err) + continue + } + if !reflect.DeepEqual(b.X, tt.post) { + t.Errorf("Unmarshal %#q into %#v: X=%#v, want %#v", blob, tt.pre, b.X, tt.post) + } + } +} + +type NullTest struct { + Bool bool + Int int + Int8 int8 + Int16 int16 + Int32 int32 + Int64 int64 + Uint uint + Uint8 uint8 + Uint16 uint16 + Uint32 uint32 + Uint64 uint64 + Float32 float32 + Float64 float64 + String string + PBool *bool + Map map[string]string + Slice []string + Interface any + + PRaw *RawMessage + PTime *time.Time + PBigInt *big.Int + PText *MustNotUnmarshalText + PBuffer *bytes.Buffer // has methods, just not relevant ones + PStruct *struct{} + + Raw RawMessage + Time time.Time + BigInt big.Int + Text MustNotUnmarshalText + Buffer bytes.Buffer + Struct struct{} +} + +// JSON null values should be ignored for primitives and string values instead of resulting in an error. +// Issue 2540 +func TestUnmarshalNulls(t *testing.T) { + // Unmarshal docs: + // The JSON null value unmarshals into an interface, map, pointer, or slice + // by setting that Go value to nil. Because null is often used in JSON to mean + // ``not present,'' unmarshaling a JSON null into any other Go type has no effect + // on the value and produces no error. + + jsonData := []byte(`{ + "Bool" : null, + "Int" : null, + "Int8" : null, + "Int16" : null, + "Int32" : null, + "Int64" : null, + "Uint" : null, + "Uint8" : null, + "Uint16" : null, + "Uint32" : null, + "Uint64" : null, + "Float32" : null, + "Float64" : null, + "String" : null, + "PBool": null, + "Map": null, + "Slice": null, + "Interface": null, + "PRaw": null, + "PTime": null, + "PBigInt": null, + "PText": null, + "PBuffer": null, + "PStruct": null, + "Raw": null, + "Time": null, + "BigInt": null, + "Text": null, + "Buffer": null, + "Struct": null + }`) + nulls := NullTest{ + Bool: true, + Int: 2, + Int8: 3, + Int16: 4, + Int32: 5, + Int64: 6, + Uint: 7, + Uint8: 8, + Uint16: 9, + Uint32: 10, + Uint64: 11, + Float32: 12.1, + Float64: 13.1, + String: "14", + PBool: new(bool), + Map: map[string]string{}, + Slice: []string{}, + Interface: new(MustNotUnmarshalJSON), + PRaw: new(RawMessage), + PTime: new(time.Time), + PBigInt: new(big.Int), + PText: new(MustNotUnmarshalText), + PStruct: new(struct{}), + PBuffer: new(bytes.Buffer), + Raw: RawMessage("123"), + Time: time.Unix(123456789, 0), + BigInt: *big.NewInt(123), + } + + before := nulls.Time.String() + + err := Unmarshal(jsonData, &nulls) + if err != nil { + t.Errorf("Unmarshal of null values failed: %v", err) + } + if !nulls.Bool || nulls.Int != 2 || nulls.Int8 != 3 || nulls.Int16 != 4 || nulls.Int32 != 5 || nulls.Int64 != 6 || + nulls.Uint != 7 || nulls.Uint8 != 8 || nulls.Uint16 != 9 || nulls.Uint32 != 10 || nulls.Uint64 != 11 || + nulls.Float32 != 12.1 || nulls.Float64 != 13.1 || nulls.String != "14" { + t.Errorf("Unmarshal of null values affected primitives") + } + + if nulls.PBool != nil { + t.Errorf("Unmarshal of null did not clear nulls.PBool") + } + if nulls.Map != nil { + t.Errorf("Unmarshal of null did not clear nulls.Map") + } + if nulls.Slice != nil { + t.Errorf("Unmarshal of null did not clear nulls.Slice") + } + if nulls.Interface != nil { + t.Errorf("Unmarshal of null did not clear nulls.Interface") + } + if nulls.PRaw != nil { + t.Errorf("Unmarshal of null did not clear nulls.PRaw") + } + if nulls.PTime != nil { + t.Errorf("Unmarshal of null did not clear nulls.PTime") + } + if nulls.PBigInt != nil { + t.Errorf("Unmarshal of null did not clear nulls.PBigInt") + } + if nulls.PText != nil { + t.Errorf("Unmarshal of null did not clear nulls.PText") + } + if nulls.PBuffer != nil { + t.Errorf("Unmarshal of null did not clear nulls.PBuffer") + } + if nulls.PStruct != nil { + t.Errorf("Unmarshal of null did not clear nulls.PStruct") + } + + if string(nulls.Raw) != "null" { + t.Errorf("Unmarshal of RawMessage null did not record null: %v", string(nulls.Raw)) + } + if nulls.Time.String() != before { + t.Errorf("Unmarshal of time.Time null set time to %v", nulls.Time.String()) + } + if nulls.BigInt.String() != "123" { + t.Errorf("Unmarshal of big.Int null set int to %v", nulls.BigInt.String()) + } +} + +type MustNotUnmarshalJSON struct{} + +func (x MustNotUnmarshalJSON) UnmarshalJSON(data []byte) error { + return errors.New("MustNotUnmarshalJSON was used") +} + +type MustNotUnmarshalText struct{} + +func (x MustNotUnmarshalText) UnmarshalText(text []byte) error { + return errors.New("MustNotUnmarshalText was used") +} + +func TestStringKind(t *testing.T) { + type stringKind string + + var m1, m2 map[stringKind]int + m1 = map[stringKind]int{ + "foo": 42, + } + + data, err := Marshal(m1) + if err != nil { + t.Errorf("Unexpected error marshaling: %v", err) + } + + err = Unmarshal(data, &m2) + if err != nil { + t.Errorf("Unexpected error unmarshaling: %v", err) + } + + if !reflect.DeepEqual(m1, m2) { + t.Error("Items should be equal after encoding and then decoding") + } +} + +// Custom types with []byte as underlying type could not be marshaled +// and then unmarshaled. +// Issue 8962. +func TestByteKind(t *testing.T) { + type byteKind []byte + + a := byteKind("hello") + + data, err := Marshal(a) + if err != nil { + t.Error(err) + } + if !reflect.DeepEqual(data, []byte(`"aGVsbG8="`)) { // MODIFIED + t.Errorf("expected %q == %q", data, `"aGVsbG8="`) // MODIFIED + } // MODIFIED + var b byteKind + err = Unmarshal(data, &b) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(a, b) { + t.Errorf("expected %v == %v", a, b) + } +} + +// The fix for issue 8962 introduced a regression. +// Issue 12921. +func TestSliceOfCustomByte(t *testing.T) { + type Uint8 uint8 + + a := []Uint8("hello") + + data, err := Marshal(a) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(data, []byte(`"aGVsbG8="`)) { // MODIFIED + t.Errorf("expected %q == %q", data, `"aGVsbG8="`) // MODIFIED + } // MODIFIED + var b []Uint8 + err = Unmarshal(data, &b) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(a, b) { + t.Fatalf("expected %v == %v", a, b) + } +} + +var decodeTypeErrorTests = []struct { + dest any + src string +}{ + {new(string), `{"user": "name"}`}, // issue 4628. + {new(error), `{}`}, // issue 4222 + {new(error), `[]`}, + {new(error), `""`}, + {new(error), `123`}, + {new(error), `true`}, +} + +func TestUnmarshalTypeError(t *testing.T) { + for _, item := range decodeTypeErrorTests { + err := Unmarshal([]byte(item.src), item.dest) + if err == nil { // if _, ok := err.(*UnmarshalTypeError); !ok { // MODIFIED + t.Errorf("expected type error for Unmarshal(%q, type %T): got %T", + item.src, item.dest, err) + } + } +} + +var unmarshalSyntaxTests = []string{ + "tru", + "fals", + "nul", + "123e", + `"hello`, + `[1,2,3`, + `{"key":1`, + `{"key":1,`, +} + +func TestUnmarshalSyntax(t *testing.T) { + var x any + for _, src := range unmarshalSyntaxTests { + err := Unmarshal([]byte(src), &x) + if err == nil { // _, ok := err.(*SyntaxError); !ok { // MODIFIED + t.Errorf("expected syntax error for Unmarshal(%q): got %T", src, err) + } + } +} + +// Test handling of unexported fields that should be ignored. +// Issue 4660 +type unexportedFields struct { + Name string + m map[string]any `json:"-"` + m2 map[string]any `json:"abcd"` + + s []int `json:"-"` +} + +func TestUnmarshalUnexported(t *testing.T) { + input := `{"Name": "Bob", "m": {"x": 123}, "m2": {"y": 456}, "abcd": {"z": 789}, "s": [2, 3]}` + want := &unexportedFields{Name: "Bob"} + + out := &unexportedFields{} + err := Unmarshal([]byte(input), out) + if err != nil { + t.Errorf("got error %v, expected nil", err) + } + if !reflect.DeepEqual(out, want) { + t.Errorf("got %q, want %q", out, want) + } +} + +// Time3339 is a time.Time which encodes to and from JSON +// as an RFC 3339 time in UTC. +type Time3339 time.Time + +func (t *Time3339) UnmarshalJSON(b []byte) error { + if len(b) < 2 || b[0] != '"' || b[len(b)-1] != '"' { + return fmt.Errorf("types: failed to unmarshal non-string value %q as an RFC 3339 time", b) + } + tm, err := time.Parse(time.RFC3339, string(b[1:len(b)-1])) + if err != nil { + return err + } + *t = Time3339(tm) + return nil +} + +func TestUnmarshalJSONLiteralError(t *testing.T) { + var t3 Time3339 + err := Unmarshal([]byte(`"0000-00-00T00:00:00Z"`), &t3) + if err == nil { + t.Fatalf("expected error; got time %v", time.Time(t3)) + } + if !strings.Contains(err.Error(), "range") { + t.Errorf("got err = %v; want out of range error", err) + } +} + +// Test that extra object elements in an array do not result in a +// "data changing underfoot" error. +// Issue 3717 +func TestSkipArrayObjects(t *testing.T) { + json := `[{}]` + var dest [0]any + + err := Unmarshal([]byte(json), &dest) + if err != nil { + t.Errorf("got error %q, want nil", err) + } +} + +// Test semantics of pre-filled data, such as struct fields, map elements, +// slices, and arrays. +// Issues 4900 and 8837, among others. +func TestPrefilled(t *testing.T) { + // Values here change, cannot reuse table across runs. + var prefillTests = []struct { + in string + ptr any + out any + }{ + { + in: `{"X": 1, "Y": 2}`, + ptr: &XYZ{X: float32(3), Y: int16(4), Z: 1.5}, + out: &XYZ{X: float64(1), Y: float64(2), Z: 1.5}, + }, + { + in: `{"X": 1, "Y": 2}`, + ptr: &map[string]any{"X": float32(3), "Y": int16(4), "Z": 1.5}, + out: &map[string]any{"X": float64(1), "Y": float64(2), "Z": 1.5}, + }, + { + in: `[2]`, + ptr: &[]int{1}, + out: &[]int{2}, + }, + { + in: `[2, 3]`, + ptr: &[]int{1}, + out: &[]int{2, 3}, + }, + { + in: `[2, 3]`, + ptr: &[...]int{1}, + out: &[...]int{2}, + }, + { + in: `[3]`, + ptr: &[...]int{1, 2}, + out: &[...]int{3, 0}, + }, + } + + for _, tt := range prefillTests { + ptrstr := fmt.Sprintf("%v", tt.ptr) + err := Unmarshal([]byte(tt.in), tt.ptr) // tt.ptr edited here + if err != nil { + t.Errorf("Unmarshal: %v", err) + } + if !reflect.DeepEqual(tt.ptr, tt.out) { + t.Errorf("Unmarshal(%#q, %s): have %v, want %v", tt.in, ptrstr, tt.ptr, tt.out) + } + } +} + +var invalidUnmarshalTests = []struct { + v any + want string +}{ + {nil, "json: Unmarshal(nil)"}, + {struct{}{}, "json: Unmarshal(non-pointer struct {})"}, + {(*int)(nil), "json: Unmarshal(nil *int)"}, +} + +func TestInvalidUnmarshal(t *testing.T) { + buf := []byte(`{"a":"1"}`) + for _, tt := range invalidUnmarshalTests { + err := Unmarshal(buf, tt.v) + if err == nil { + t.Errorf("Unmarshal expecting error, got nil") + continue + } + if got := err.Error(); got != tt.want { + t.Errorf("Unmarshal = %q; want %q", got, tt.want) + } + } +} + +var invalidUnmarshalTextTests = []struct { + v any + want string +}{ + {nil, "json: Unmarshal(nil)"}, + {struct{}{}, "json: Unmarshal(non-pointer struct {})"}, + {(*int)(nil), "json: Unmarshal(nil *int)"}, + {new(net.IP), "json: cannot unmarshal number into Go value of type *net.IP"}, +} + +func TestInvalidUnmarshalText(t *testing.T) { + buf := []byte(`123`) + for _, tt := range invalidUnmarshalTextTests { + err := Unmarshal(buf, tt.v) + if err == nil { + t.Errorf("Unmarshal expecting error, got nil") + continue + } + // if got := err.Error(); got != tt.want { // MODIFIED + // t.Errorf("Unmarshal = %q; want %q", got, tt.want) // MODIFIED + // } // MODIFIED + } +} + +// Test that string option is ignored for invalid types. +// Issue 9812. +func TestInvalidStringOption(t *testing.T) { + num := 0 + item := struct { + T time.Time `json:",string"` + M map[string]string `json:",string"` + S []string `json:",string"` + A [1]string `json:",string"` + I any `json:",string"` + P *int `json:",string"` + }{M: make(map[string]string), S: make([]string, 0), I: num, P: &num} + + data, err := Marshal(item) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + + err = Unmarshal(data, &item) + if err != nil { + t.Fatalf("Unmarshal: %v", err) + } +} + +// Test unmarshal behavior with regards to embedded unexported structs. +// +// (Issue 21357) If the embedded struct is a pointer and is unallocated, +// this returns an error because unmarshal cannot set the field. +// +// (Issue 24152) If the embedded struct is given an explicit name, +// ensure that the normal unmarshal logic does not panic in reflect. +// +// (Issue 28145) If the embedded struct is given an explicit name and has +// exported methods, don't cause a panic trying to get its value. +func TestUnmarshalEmbeddedUnexported(t *testing.T) { + t.Skip() // TODO + type ( + embed1 struct{ Q int } + embed2 struct{ Q int } + embed3 struct { + Q int64 `json:",string"` + } + S1 struct { + *embed1 + R int + } + S2 struct { + *embed1 + Q int + } + S3 struct { + embed1 + R int + } + S4 struct { + *embed1 + embed2 + } + S5 struct { + *embed3 + R int + } + S6 struct { + embed1 `json:"embed1"` + } + S7 struct { + embed1 `json:"embed1"` + embed2 + } + S8 struct { + embed1 `json:"embed1"` + embed2 `json:"embed2"` + Q int + } + S9 struct { + unexportedWithMethods `json:"embed"` + } + ) + + tests := []struct { + in string + ptr any + out any + err error + }{{ + // Error since we cannot set S1.embed1, but still able to set S1.R. + in: `{"R":2,"Q":1}`, + ptr: new(S1), + out: &S1{R: 2}, + err: fmt.Errorf("json: cannot set embedded pointer to unexported struct: json.embed1"), + }, { + // The top level Q field takes precedence. + in: `{"Q":1}`, + ptr: new(S2), + out: &S2{Q: 1}, + }, { + // No issue with non-pointer variant. + in: `{"R":2,"Q":1}`, + ptr: new(S3), + out: &S3{embed1: embed1{Q: 1}, R: 2}, + }, { + // No error since both embedded structs have field R, which annihilate each other. + // Thus, no attempt is made at setting S4.embed1. + in: `{"R":2}`, + ptr: new(S4), + out: new(S4), + }, { + // Error since we cannot set S5.embed1, but still able to set S5.R. + in: `{"R":2,"Q":1}`, + ptr: new(S5), + out: &S5{R: 2}, + err: fmt.Errorf("json: cannot set embedded pointer to unexported struct: json.embed3"), + }, { + // Issue 24152, ensure decodeState.indirect does not panic. + in: `{"embed1": {"Q": 1}}`, + ptr: new(S6), + out: &S6{embed1{1}}, + }, { + // Issue 24153, check that we can still set forwarded fields even in + // the presence of a name conflict. + // + // This relies on obscure behavior of reflect where it is possible + // to set a forwarded exported field on an unexported embedded struct + // even though there is a name conflict, even when it would have been + // impossible to do so according to Go visibility rules. + // Go forbids this because it is ambiguous whether S7.Q refers to + // S7.embed1.Q or S7.embed2.Q. Since embed1 and embed2 are unexported, + // it should be impossible for an external package to set either Q. + // + // It is probably okay for a future reflect change to break this. + in: `{"embed1": {"Q": 1}, "Q": 2}`, + ptr: new(S7), + out: &S7{embed1{1}, embed2{2}}, + }, { + // Issue 24153, similar to the S7 case. + in: `{"embed1": {"Q": 1}, "embed2": {"Q": 2}, "Q": 3}`, + ptr: new(S8), + out: &S8{embed1{1}, embed2{2}, 3}, + }, { + // Issue 228145, similar to the cases above. + in: `{"embed": {}}`, + ptr: new(S9), + out: &S9{}, + }} + + for i, tt := range tests { + err := Unmarshal([]byte(tt.in), tt.ptr) + if !equalError(err, tt.err) { + t.Errorf("#%d: %v, want %v", i, err, tt.err) + } + if !reflect.DeepEqual(tt.ptr, tt.out) { + t.Errorf("#%d: mismatch\ngot: %#+v\nwant: %#+v", i, tt.ptr, tt.out) + } + } +} + +func TestUnmarshalErrorAfterMultipleJSON(t *testing.T) { + t.Skip() // TODO + tests := []struct { + in string + err error + }{{ + in: `1 false null :`, + err: &SyntaxError{"invalid character ':' looking for beginning of value", 14}, + }, { + in: `1 [] [,]`, + err: &SyntaxError{"invalid character ',' looking for beginning of value", 7}, + }, { + in: `1 [] [true:]`, + err: &SyntaxError{"invalid character ':' after array element", 11}, + }, { + in: `1 {} {"x"=}`, + err: &SyntaxError{"invalid character '=' after object key", 14}, + }, { + in: `falsetruenul#`, + err: &SyntaxError{"invalid character '#' in literal null (expecting 'l')", 13}, + }} + for i, tt := range tests { + dec := NewDecoder(strings.NewReader(tt.in)) + var err error + for { + var v any + if err = dec.Decode(&v); err != nil { + break + } + } + if !reflect.DeepEqual(err, tt.err) { + t.Errorf("#%d: got %#v, want %#v", i, err, tt.err) + } + } +} + +type unmarshalPanic struct{} + +func (unmarshalPanic) UnmarshalJSON([]byte) error { panic(0xdead) } + +func TestUnmarshalPanic(t *testing.T) { + defer func() { + if got := recover(); !reflect.DeepEqual(got, 0xdead) { + t.Errorf("panic() = (%T)(%v), want 0xdead", got, got) + } + }() + Unmarshal([]byte("{}"), &unmarshalPanic{}) + t.Fatalf("Unmarshal should have panicked") +} + +// The decoder used to hang if decoding into an interface pointing to its own address. +// See golang.org/issues/31740. +func TestUnmarshalRecursivePointer(t *testing.T) { + t.Skip() // TODO + var v any + v = &v + data := []byte(`{"a": "b"}`) + + if err := Unmarshal(data, v); err != nil { + t.Fatal(err) + } +} + +type textUnmarshalerString string + +func (m *textUnmarshalerString) UnmarshalText(text []byte) error { + *m = textUnmarshalerString(strings.ToLower(string(text))) + return nil +} + +// Test unmarshal to a map, where the map key is a user defined type. +// See golang.org/issues/34437. +func TestUnmarshalMapWithTextUnmarshalerStringKey(t *testing.T) { + var p map[textUnmarshalerString]string + if err := Unmarshal([]byte(`{"FOO": "1"}`), &p); err != nil { + t.Fatalf("Unmarshal unexpected error: %v", err) + } + + if _, ok := p["foo"]; !ok { + t.Errorf(`Key "foo" does not exist in map: %v`, p) + } +} + +func TestUnmarshalRescanLiteralMangledUnquote(t *testing.T) { + // See golang.org/issues/38105. + var p map[textUnmarshalerString]string + if err := Unmarshal([]byte(`{"开源":"12345开源"}`), &p); err != nil { + t.Fatalf("Unmarshal unexpected error: %v", err) + } + if _, ok := p["开源"]; !ok { + t.Errorf(`Key "开源" does not exist in map: %v`, p) + } + + // See golang.org/issues/38126. + type T struct { + F1 string `json:"F1,string"` + } + t1 := T{"aaa\tbbb"} + + b, err := Marshal(t1) + if err != nil { + t.Fatalf("Marshal unexpected error: %v", err) + } + var t2 T + if err := Unmarshal(b, &t2); err != nil { + t.Fatalf("Unmarshal unexpected error: %v", err) + } + if t1 != t2 { + t.Errorf("Marshal and Unmarshal roundtrip mismatch: want %q got %q", t1, t2) + } + + // See golang.org/issues/39555. + input := map[textUnmarshalerString]string{"FOO": "", `"`: ""} + + encoded, err := Marshal(input) + if err != nil { + t.Fatalf("Marshal unexpected error: %v", err) + } + var got map[textUnmarshalerString]string + if err := Unmarshal(encoded, &got); err != nil { + t.Fatalf("Unmarshal unexpected error: %v", err) + } + want := map[textUnmarshalerString]string{"foo": "", `"`: ""} + if !reflect.DeepEqual(want, got) { + t.Fatalf("Unexpected roundtrip result:\nwant: %q\ngot: %q", want, got) + } +} + +func TestUnmarshalMaxDepth(t *testing.T) { + t.Skip() // TODO + testcases := []struct { + name string + data string + errMaxDepth bool + }{ + { + name: "ArrayUnderMaxNestingDepth", + data: `{"a":` + strings.Repeat(`[`, 10000-1) + strings.Repeat(`]`, 10000-1) + `}`, + errMaxDepth: false, + }, + { + name: "ArrayOverMaxNestingDepth", + data: `{"a":` + strings.Repeat(`[`, 10000) + strings.Repeat(`]`, 10000) + `}`, + errMaxDepth: true, + }, + { + name: "ArrayOverStackDepth", + data: `{"a":` + strings.Repeat(`[`, 3000000) + strings.Repeat(`]`, 3000000) + `}`, + errMaxDepth: true, + }, + { + name: "ObjectUnderMaxNestingDepth", + data: `{"a":` + strings.Repeat(`{"a":`, 10000-1) + `0` + strings.Repeat(`}`, 10000-1) + `}`, + errMaxDepth: false, + }, + { + name: "ObjectOverMaxNestingDepth", + data: `{"a":` + strings.Repeat(`{"a":`, 10000) + `0` + strings.Repeat(`}`, 10000) + `}`, + errMaxDepth: true, + }, + { + name: "ObjectOverStackDepth", + data: `{"a":` + strings.Repeat(`{"a":`, 3000000) + `0` + strings.Repeat(`}`, 3000000) + `}`, + errMaxDepth: true, + }, + } + + targets := []struct { + name string + newValue func() any + }{ + { + name: "unstructured", + newValue: func() any { + var v any + return &v + }, + }, + { + name: "typed named field", + newValue: func() any { + v := struct { + A any `json:"a"` + }{} + return &v + }, + }, + { + name: "typed missing field", + newValue: func() any { + v := struct { + B any `json:"b"` + }{} + return &v + }, + }, + { + name: "custom unmarshaler", + newValue: func() any { + v := unmarshaler{} + return &v + }, + }, + } + + for _, tc := range testcases { + for _, target := range targets { + t.Run(target.name+"-"+tc.name, func(t *testing.T) { + err := Unmarshal([]byte(tc.data), target.newValue()) + if !tc.errMaxDepth { + if err != nil { + t.Errorf("unexpected error: %v", err) + } + } else { + if err == nil { + t.Errorf("expected error containing 'exceeded max depth', got none") + } else if !strings.Contains(err.Error(), "exceeded max depth") { + t.Errorf("expected error containing 'exceeded max depth', got: %v", err) + } + } + }) + } + } +} diff --git a/borrowed_encode_test.go b/borrowed_encode_test.go new file mode 100644 index 0000000..9659910 --- /dev/null +++ b/borrowed_encode_test.go @@ -0,0 +1,1208 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package lowmemjson // MODIFIED + +import ( + "bytes" + "encoding" + "fmt" + "log" + "math" + "reflect" + "regexp" + "strconv" + "testing" + "unicode" +) + +type Optionals struct { + Sr string `json:"sr"` + So string `json:"so,omitempty"` + Sw string `json:"-"` + + Ir int `json:"omitempty"` // actually named omitempty, not an option + Io int `json:"io,omitempty"` + + Slr []string `json:"slr,random"` //nolint:staticcheck // testing handling of unknown options // MODIFIED + Slo []string `json:"slo,omitempty"` + + Mr map[string]any `json:"mr"` + Mo map[string]any `json:",omitempty"` + + Fr float64 `json:"fr"` + Fo float64 `json:"fo,omitempty"` + + Br bool `json:"br"` + Bo bool `json:"bo,omitempty"` + + Ur uint `json:"ur"` + Uo uint `json:"uo,omitempty"` + + Str struct{} `json:"str"` + Sto struct{} `json:"sto,omitempty"` +} + +var optionalsExpected = `{ + "sr": "", + "omitempty": 0, + "slr": null, + "mr": {}, + "fr": 0, + "br": false, + "ur": 0, + "str": {}, + "sto": {} +}` + +func TestOmitEmpty(t *testing.T) { + var o Optionals + o.Sw = "something" + o.Mr = map[string]any{} + o.Mo = map[string]any{} + + got, err := MarshalIndent(&o, "", " ") + if err != nil { + t.Fatal(err) + } + if got := string(got); got != optionalsExpected { + t.Errorf(" got: %s\nwant: %s\n", got, optionalsExpected) + } +} + +type StringTag struct { + BoolStr bool `json:",string"` + IntStr int64 `json:",string"` + UintptrStr uintptr `json:",string"` + StrStr string `json:",string"` + NumberStr Number `json:",string"` +} + +func TestRoundtripStringTag(t *testing.T) { + tests := []struct { + name string + in StringTag + want string // empty to just test that we roundtrip + }{ + { + name: "AllTypes", + in: StringTag{ + BoolStr: true, + IntStr: 42, + UintptrStr: 44, + StrStr: "xzbit", + NumberStr: "46", + }, + want: `{ + "BoolStr": "true", + "IntStr": "42", + "UintptrStr": "44", + "StrStr": "\"xzbit\"", + "NumberStr": "46" + }`, + }, + { + // See golang.org/issues/38173. + name: "StringDoubleEscapes", + in: StringTag{ + StrStr: "\b\f\n\r\t\"\\", + NumberStr: "0", // just to satisfy the roundtrip + }, + want: `{ + "BoolStr": "false", + "IntStr": "0", + "UintptrStr": "0", + "StrStr": "\"\\u0008\\u000c\\n\\r\\t\\\"\\\\\"", + "NumberStr": "0" + }`, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + // Indent with a tab prefix to make the multi-line string + // literals in the table nicer to read. + got, err := MarshalIndent(&test.in, "\t\t\t", "\t") + if err != nil { + t.Fatal(err) + } + if got := string(got); got != test.want { + t.Fatalf(" got: %s\nwant: %s\n", got, test.want) + } + + // Verify that it round-trips. + var s2 StringTag + if err := Unmarshal(got, &s2); err != nil { + t.Fatalf("Decode: %v", err) + } + if !reflect.DeepEqual(test.in, s2) { + t.Fatalf("decode didn't match.\nsource: %#v\nEncoded as:\n%s\ndecode: %#v", test.in, string(got), s2) + } + }) + } +} + +// byte slices are special even if they're renamed types. +type renamedByte byte +type renamedByteSlice []byte +type renamedRenamedByteSlice []renamedByte + +func TestEncodeRenamedByteSlice(t *testing.T) { + s := renamedByteSlice("abc") + result, err := Marshal(s) + if err != nil { + t.Fatal(err) + } + expect := `"YWJj"` + if string(result) != expect { + t.Errorf(" got %s want %s", result, expect) + } + r := renamedRenamedByteSlice("abc") + result, err = Marshal(r) + if err != nil { + t.Fatal(err) + } + if string(result) != expect { + t.Errorf(" got %s want %s", result, expect) + } +} + +type SamePointerNoCycle struct { + Ptr1, Ptr2 *SamePointerNoCycle +} + +var samePointerNoCycle = &SamePointerNoCycle{} + +type PointerCycle struct { + Ptr *PointerCycle +} + +var pointerCycle = &PointerCycle{} + +type PointerCycleIndirect struct { + Ptrs []any +} + +type RecursiveSlice []RecursiveSlice + +var ( + pointerCycleIndirect = &PointerCycleIndirect{} + mapCycle = make(map[string]any) + sliceCycle = []any{nil} + sliceNoCycle = []any{nil, nil} + recursiveSliceCycle = []RecursiveSlice{nil} +) + +func init() { + ptr := &SamePointerNoCycle{} + samePointerNoCycle.Ptr1 = ptr + samePointerNoCycle.Ptr2 = ptr + + pointerCycle.Ptr = pointerCycle + pointerCycleIndirect.Ptrs = []any{pointerCycleIndirect} + + mapCycle["x"] = mapCycle + sliceCycle[0] = sliceCycle + sliceNoCycle[1] = sliceNoCycle[:1] + for i := startDetectingCyclesAfter; i > 0; i-- { + sliceNoCycle = []any{sliceNoCycle} + } + recursiveSliceCycle[0] = recursiveSliceCycle +} + +func TestSamePointerNoCycle(t *testing.T) { + if _, err := Marshal(samePointerNoCycle); err != nil { + t.Fatalf("unexpected error: %v", err) + } +} + +func TestSliceNoCycle(t *testing.T) { + if _, err := Marshal(sliceNoCycle); err != nil { + t.Fatalf("unexpected error: %v", err) + } +} + +var unsupportedValues = []any{ + math.NaN(), + math.Inf(-1), + math.Inf(1), + //pointerCycle, // MODIFIED + //pointerCycleIndirect, // MODIFIED + //mapCycle, // MODIFIED + //sliceCycle, // MODIFIED + //recursiveSliceCycle, // MODIFIED +} + +func TestUnsupportedValues(t *testing.T) { + for _, v := range unsupportedValues { + if _, err := Marshal(v); err != nil { + if _, ok := err.(*UnsupportedValueError); !ok { + t.Errorf("for %v, got %T want UnsupportedValueError", v, err) + } + } else { + t.Errorf("for %v, expected error", v) + } + } +} + +// Issue 43207 +func TestMarshalTextFloatMap(t *testing.T) { + m := map[textfloat]string{ + textfloat(math.NaN()): "1", + textfloat(math.NaN()): "1", + } + got, err := Marshal(m) + if err != nil { + t.Errorf("Marshal() error: %v", err) + } + want := `{"TF:NaN":"1","TF:NaN":"1"}` + if string(got) != want { + t.Errorf("Marshal() = %s, want %s", got, want) + } +} + +// Ref has Marshaler and Unmarshaler methods with pointer receiver. +type Ref int + +func (*Ref) MarshalJSON() ([]byte, error) { + return []byte(`"ref"`), nil +} + +func (r *Ref) UnmarshalJSON([]byte) error { + *r = 12 + return nil +} + +// Val has Marshaler methods with value receiver. +type Val int + +func (Val) MarshalJSON() ([]byte, error) { + return []byte(`"val"`), nil +} + +// RefText has Marshaler and Unmarshaler methods with pointer receiver. +type RefText int + +func (*RefText) MarshalText() ([]byte, error) { + return []byte(`"ref"`), nil +} + +func (r *RefText) UnmarshalText([]byte) error { + *r = 13 + return nil +} + +// ValText has Marshaler methods with value receiver. +type ValText int + +func (ValText) MarshalText() ([]byte, error) { + return []byte(`"val"`), nil +} + +func TestRefValMarshal(t *testing.T) { + var s = struct { + R0 Ref + R1 *Ref + R2 RefText + R3 *RefText + V0 Val + V1 *Val + V2 ValText + V3 *ValText + }{ + R0: 12, + R1: new(Ref), + R2: 14, + R3: new(RefText), + V0: 13, + V1: new(Val), + V2: 15, + V3: new(ValText), + } + const want = `{"R0":"ref","R1":"ref","R2":"\"ref\"","R3":"\"ref\"","V0":"val","V1":"val","V2":"\"val\"","V3":"\"val\""}` + b, err := Marshal(&s) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + if got := string(b); got != want { + t.Errorf("got %q, want %q", got, want) + } +} + +// C implements Marshaler and returns unescaped JSON. +type C int + +func (C) MarshalJSON() ([]byte, error) { + return []byte(`"<&>"`), nil +} + +// CText implements Marshaler and returns unescaped text. +type CText int + +func (CText) MarshalText() ([]byte, error) { + return []byte(`"<&>"`), nil +} + +func TestMarshalerEscaping(t *testing.T) { + t.Skip() // MODIFIED + var c C + want := `"\u003c\u0026\u003e"` + b, err := Marshal(c) + if err != nil { + t.Fatalf("Marshal(c): %v", err) + } + if got := string(b); got != want { + t.Errorf("Marshal(c) = %#q, want %#q", got, want) + } + + var ct CText + want = `"\"\u003c\u0026\u003e\""` + b, err = Marshal(ct) + if err != nil { + t.Fatalf("Marshal(ct): %v", err) + } + if got := string(b); got != want { + t.Errorf("Marshal(ct) = %#q, want %#q", got, want) + } +} + +func TestAnonymousFields(t *testing.T) { + tests := []struct { + label string // Test name + makeInput func() any // Function to create input value + want string // Expected JSON output + }{{ + // Both S1 and S2 have a field named X. From the perspective of S, + // it is ambiguous which one X refers to. + // This should not serialize either field. + label: "AmbiguousField", + makeInput: func() any { + type ( + S1 struct{ x, X int } + S2 struct{ x, X int } + S struct { + S1 + S2 + } + ) + return S{S1{1, 2}, S2{3, 4}} + }, + want: `{}`, + }, { + label: "DominantField", + // Both S1 and S2 have a field named X, but since S has an X field as + // well, it takes precedence over S1.X and S2.X. + makeInput: func() any { + type ( + S1 struct{ x, X int } + S2 struct{ x, X int } + S struct { + S1 + S2 + x, X int + } + ) + return S{S1{1, 2}, S2{3, 4}, 5, 6} + }, + want: `{"X":6}`, + }, { + // Unexported embedded field of non-struct type should not be serialized. + label: "UnexportedEmbeddedInt", + makeInput: func() any { + type ( + myInt int + S struct{ myInt } + ) + return S{5} + }, + want: `{}`, + }, { + // Exported embedded field of non-struct type should be serialized. + label: "ExportedEmbeddedInt", + makeInput: func() any { + type ( + MyInt int + S struct{ MyInt } + ) + return S{5} + }, + want: `{"MyInt":5}`, + }, { + // Unexported embedded field of pointer to non-struct type + // should not be serialized. + label: "UnexportedEmbeddedIntPointer", + makeInput: func() any { + type ( + myInt int + S struct{ *myInt } + ) + s := S{new(myInt)} + *s.myInt = 5 + return s + }, + want: `{}`, + }, { + // Exported embedded field of pointer to non-struct type + // should be serialized. + label: "ExportedEmbeddedIntPointer", + makeInput: func() any { + type ( + MyInt int + S struct{ *MyInt } + ) + s := S{new(MyInt)} + *s.MyInt = 5 + return s + }, + want: `{"MyInt":5}`, + }, { + // Exported fields of embedded structs should have their + // exported fields be serialized regardless of whether the struct types + // themselves are exported. + label: "EmbeddedStruct", + makeInput: func() any { + type ( + s1 struct{ x, X int } + S2 struct{ y, Y int } + S struct { + s1 + S2 + } + ) + return S{s1{1, 2}, S2{3, 4}} + }, + want: `{"X":2,"Y":4}`, + }, { + // Exported fields of pointers to embedded structs should have their + // exported fields be serialized regardless of whether the struct types + // themselves are exported. + label: "EmbeddedStructPointer", + makeInput: func() any { + type ( + s1 struct{ x, X int } + S2 struct{ y, Y int } + S struct { + *s1 + *S2 + } + ) + return S{&s1{1, 2}, &S2{3, 4}} + }, + want: `{"X":2,"Y":4}`, + }, { + // Exported fields on embedded unexported structs at multiple levels + // of nesting should still be serialized. + label: "NestedStructAndInts", + makeInput: func() any { + type ( + MyInt1 int + MyInt2 int + myInt int + s2 struct { + MyInt2 + myInt + } + s1 struct { + MyInt1 + myInt + s2 + } + S struct { + s1 + myInt + } + ) + return S{s1{1, 2, s2{3, 4}}, 6} + }, + want: `{"MyInt1":1,"MyInt2":3}`, + }, { + // If an anonymous struct pointer field is nil, we should ignore + // the embedded fields behind it. Not properly doing so may + // result in the wrong output or reflect panics. + label: "EmbeddedFieldBehindNilPointer", + makeInput: func() any { + type ( + S2 struct{ Field string } + S struct{ *S2 } + ) + return S{} + }, + want: `{}`, + }} + + for _, tt := range tests { + t.Run(tt.label, func(t *testing.T) { + b, err := Marshal(tt.makeInput()) + if err != nil { + t.Fatalf("Marshal() = %v, want nil error", err) + } + if string(b) != tt.want { + t.Fatalf("Marshal() = %q, want %q", b, tt.want) + } + }) + } +} + +type BugA struct { + S string +} + +type BugB struct { + BugA + S string +} + +type BugC struct { + S string +} + +// Legal Go: We never use the repeated embedded field (S). +type BugX struct { + A int + BugA + BugB +} + +// golang.org/issue/16042. +// Even if a nil interface value is passed in, as long as +// it implements Marshaler, it should be marshaled. +type nilJSONMarshaler string + +func (nm *nilJSONMarshaler) MarshalJSON() ([]byte, error) { + if nm == nil { + return Marshal("0zenil0") + } + return Marshal("zenil:" + string(*nm)) +} + +// golang.org/issue/34235. +// Even if a nil interface value is passed in, as long as +// it implements encoding.TextMarshaler, it should be marshaled. +type nilTextMarshaler string + +func (nm *nilTextMarshaler) MarshalText() ([]byte, error) { + if nm == nil { + return []byte("0zenil0"), nil + } + return []byte("zenil:" + string(*nm)), nil +} + +// See golang.org/issue/16042 and golang.org/issue/34235. +func TestNilMarshal(t *testing.T) { + testCases := []struct { + v any + want string + }{ + {v: nil, want: `null`}, + {v: new(float64), want: `0`}, + {v: []any(nil), want: `null`}, + {v: []string(nil), want: `null`}, + {v: map[string]string(nil), want: `null`}, + {v: []byte(nil), want: `null`}, + {v: struct{ M string }{"gopher"}, want: `{"M":"gopher"}`}, + {v: struct{ M Marshaler }{}, want: `{"M":null}`}, + {v: struct{ M Marshaler }{(*nilJSONMarshaler)(nil)}, want: `{"M":"0zenil0"}`}, + {v: struct{ M any }{(*nilJSONMarshaler)(nil)}, want: `{"M":null}`}, + {v: struct{ M encoding.TextMarshaler }{}, want: `{"M":null}`}, + {v: struct{ M encoding.TextMarshaler }{(*nilTextMarshaler)(nil)}, want: `{"M":"0zenil0"}`}, + {v: struct{ M any }{(*nilTextMarshaler)(nil)}, want: `{"M":null}`}, + } + + for _, tt := range testCases { + out, err := Marshal(tt.v) + if err != nil || string(out) != tt.want { + t.Errorf("Marshal(%#v) = %#q, %#v, want %#q, nil", tt.v, out, err, tt.want) + continue + } + } +} + +// Issue 5245. +func TestEmbeddedBug(t *testing.T) { + v := BugB{ + BugA{"A"}, + "B", + } + b, err := Marshal(v) + if err != nil { + t.Fatal("Marshal:", err) + } + want := `{"S":"B"}` + got := string(b) + if got != want { + t.Fatalf("Marshal: got %s want %s", got, want) + } + // Now check that the duplicate field, S, does not appear. + x := BugX{ + A: 23, + } + b, err = Marshal(x) + if err != nil { + t.Fatal("Marshal:", err) + } + want = `{"A":23}` + got = string(b) + if got != want { + t.Fatalf("Marshal: got %s want %s", got, want) + } +} + +type BugD struct { // Same as BugA after tagging. + XXX string `json:"S"` +} + +// BugD's tagged S field should dominate BugA's. +type BugY struct { + BugA + BugD +} + +// Test that a field with a tag dominates untagged fields. +func TestTaggedFieldDominates(t *testing.T) { + v := BugY{ + BugA{"BugA"}, + BugD{"BugD"}, + } + b, err := Marshal(v) + if err != nil { + t.Fatal("Marshal:", err) + } + want := `{"S":"BugD"}` + got := string(b) + if got != want { + t.Fatalf("Marshal: got %s want %s", got, want) + } +} + +// There are no tags here, so S should not appear. +type BugZ struct { + BugA + BugC + BugY // Contains a tagged S field through BugD; should not dominate. +} + +func TestDuplicatedFieldDisappears(t *testing.T) { + v := BugZ{ + BugA{"BugA"}, + BugC{"BugC"}, + BugY{ + BugA{"nested BugA"}, + BugD{"nested BugD"}, + }, + } + b, err := Marshal(v) + if err != nil { + t.Fatal("Marshal:", err) + } + want := `{}` + got := string(b) + if got != want { + t.Fatalf("Marshal: got %s want %s", got, want) + } +} + +func TestStringBytes(t *testing.T) { + t.Parallel() + // Test that encodeState.stringBytes and encodeState.string use the same encoding. + var r []rune + for i := '\u0000'; i <= unicode.MaxRune; i++ { + if testing.Short() && i > 1000 { + i = unicode.MaxRune + } + r = append(r, i) + } + s := string(r) + "\xff\xff\xffhello" // some invalid UTF-8 too + + for _, escapeHTML := range []bool{true, false} { + es := &encodeState{} + es.string(s, escapeHTML) + + esBytes := &encodeState{} + esBytes.stringBytes([]byte(s), escapeHTML) + + enc := es.Buffer.String() + encBytes := esBytes.Buffer.String() + if enc != encBytes { + i := 0 + for i < len(enc) && i < len(encBytes) && enc[i] == encBytes[i] { + i++ + } + enc = enc[i:] + encBytes = encBytes[i:] + i = 0 + for i < len(enc) && i < len(encBytes) && enc[len(enc)-i-1] == encBytes[len(encBytes)-i-1] { + i++ + } + enc = enc[:len(enc)-i] + encBytes = encBytes[:len(encBytes)-i] + + if len(enc) > 20 { + enc = enc[:20] + "..." + } + if len(encBytes) > 20 { + encBytes = encBytes[:20] + "..." + } + + t.Errorf("with escapeHTML=%t, encodings differ at %#q vs %#q", + escapeHTML, enc, encBytes) + } + } +} + +func TestIssue10281(t *testing.T) { + type Foo struct { + N Number + } + x := Foo{Number(`invalid`)} + + b, err := Marshal(&x) + if err == nil { + t.Errorf("Marshal(&x) = %#q; want error", b) + } +} + +func TestHTMLEscape(t *testing.T) { + var b, want bytes.Buffer + m := `{"M":"foo &` + "\xe2\x80\xa8 \xe2\x80\xa9" + `"}` + want.Write([]byte(`{"M":"\u003chtml\u003efoo \u0026\u2028 \u2029\u003c/html\u003e"}`)) + HTMLEscape(&b, []byte(m)) + if !bytes.Equal(b.Bytes(), want.Bytes()) { + t.Errorf("HTMLEscape(&b, []byte(m)) = %s; want %s", b.Bytes(), want.Bytes()) + } +} + +// golang.org/issue/8582 +func TestEncodePointerString(t *testing.T) { + type stringPointer struct { + N *int64 `json:"n,string"` + } + var n int64 = 42 + b, err := Marshal(stringPointer{N: &n}) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + if got, want := string(b), `{"n":"42"}`; got != want { + t.Errorf("Marshal = %s, want %s", got, want) + } + var back stringPointer + err = Unmarshal(b, &back) + if err != nil { + t.Fatalf("Unmarshal: %v", err) + } + if back.N == nil { + t.Fatalf("Unmarshaled nil N field") + } + if *back.N != 42 { + t.Fatalf("*N = %d; want 42", *back.N) + } +} + +var encodeStringTests = []struct { + in string + out string +}{ + {"\x00", `"\u0000"`}, + {"\x01", `"\u0001"`}, + {"\x02", `"\u0002"`}, + {"\x03", `"\u0003"`}, + {"\x04", `"\u0004"`}, + {"\x05", `"\u0005"`}, + {"\x06", `"\u0006"`}, + {"\x07", `"\u0007"`}, + {"\x08", `"\u0008"`}, + {"\x09", `"\t"`}, + {"\x0a", `"\n"`}, + {"\x0b", `"\u000b"`}, + {"\x0c", `"\u000c"`}, + {"\x0d", `"\r"`}, + {"\x0e", `"\u000e"`}, + {"\x0f", `"\u000f"`}, + {"\x10", `"\u0010"`}, + {"\x11", `"\u0011"`}, + {"\x12", `"\u0012"`}, + {"\x13", `"\u0013"`}, + {"\x14", `"\u0014"`}, + {"\x15", `"\u0015"`}, + {"\x16", `"\u0016"`}, + {"\x17", `"\u0017"`}, + {"\x18", `"\u0018"`}, + {"\x19", `"\u0019"`}, + {"\x1a", `"\u001a"`}, + {"\x1b", `"\u001b"`}, + {"\x1c", `"\u001c"`}, + {"\x1d", `"\u001d"`}, + {"\x1e", `"\u001e"`}, + {"\x1f", `"\u001f"`}, +} + +func TestEncodeString(t *testing.T) { + for _, tt := range encodeStringTests { + b, err := Marshal(tt.in) + if err != nil { + t.Errorf("Marshal(%q): %v", tt.in, err) + continue + } + out := string(b) + if out != tt.out { + t.Errorf("Marshal(%q) = %#q, want %#q", tt.in, out, tt.out) + } + } +} + +type jsonbyte byte + +func (b jsonbyte) MarshalJSON() ([]byte, error) { return tenc(`{"JB":%d}`, b) } + +type textbyte byte + +func (b textbyte) MarshalText() ([]byte, error) { return tenc(`TB:%d`, b) } + +type jsonint int + +func (i jsonint) MarshalJSON() ([]byte, error) { return tenc(`{"JI":%d}`, i) } + +type textint int + +func (i textint) MarshalText() ([]byte, error) { return tenc(`TI:%d`, i) } + +func tenc(format string, a ...any) ([]byte, error) { + var buf bytes.Buffer + fmt.Fprintf(&buf, format, a...) + return buf.Bytes(), nil +} + +type textfloat float64 + +func (f textfloat) MarshalText() ([]byte, error) { return tenc(`TF:%0.2f`, f) } + +// Issue 13783 +func TestEncodeBytekind(t *testing.T) { + t.Skip() // TODO + testdata := []struct { + data any + want string + }{ + {byte(7), "7"}, + {jsonbyte(7), `{"JB":7}`}, + {textbyte(4), `"TB:4"`}, + {jsonint(5), `{"JI":5}`}, + {textint(1), `"TI:1"`}, + {[]byte{0, 1}, `"AAE="`}, + {[]jsonbyte{0, 1}, `[{"JB":0},{"JB":1}]`}, + {[][]jsonbyte{{0, 1}, {3}}, `[[{"JB":0},{"JB":1}],[{"JB":3}]]`}, + {[]textbyte{2, 3}, `["TB:2","TB:3"]`}, + {[]jsonint{5, 4}, `[{"JI":5},{"JI":4}]`}, + {[]textint{9, 3}, `["TI:9","TI:3"]`}, + {[]int{9, 3}, `[9,3]`}, + {[]textfloat{12, 3}, `["TF:12.00","TF:3.00"]`}, + } + for _, d := range testdata { + js, err := Marshal(d.data) + if err != nil { + t.Error(err) + continue + } + got, want := string(js), d.want + if got != want { + t.Errorf("got %s, want %s", got, want) + } + } +} + +func TestTextMarshalerMapKeysAreSorted(t *testing.T) { + b, err := Marshal(map[unmarshalerText]int{ + {"x", "y"}: 1, + {"y", "x"}: 2, + {"a", "z"}: 3, + {"z", "a"}: 4, + }) + if err != nil { + t.Fatalf("Failed to Marshal text.Marshaler: %v", err) + } + const want = `{"a:z":3,"x:y":1,"y:x":2,"z:a":4}` + if string(b) != want { + t.Errorf("Marshal map with text.Marshaler keys: got %#q, want %#q", b, want) + } +} + +// https://golang.org/issue/33675 +func TestNilMarshalerTextMapKey(t *testing.T) { + b, err := Marshal(map[*unmarshalerText]int{ + (*unmarshalerText)(nil): 1, + {"A", "B"}: 2, + }) + if err != nil { + t.Fatalf("Failed to Marshal *text.Marshaler: %v", err) + } + const want = `{"":1,"A:B":2}` + if string(b) != want { + t.Errorf("Marshal map with *text.Marshaler keys: got %#q, want %#q", b, want) + } +} + +var re = regexp.MustCompile + +// syntactic checks on form of marshaled floating point numbers. +var badFloatREs = []*regexp.Regexp{ + re(`p`), // no binary exponential notation + re(`^\+`), // no leading + sign + re(`^-?0[^.]`), // no unnecessary leading zeros + re(`^-?\.`), // leading zero required before decimal point + re(`\.(e|$)`), // no trailing decimal + re(`\.[0-9]+0(e|$)`), // no trailing zero in fraction + re(`^-?(0|[0-9]{2,})\..*e`), // exponential notation must have normalized mantissa + re(`e[0-9]`), // positive exponent must be signed + re(`e[+-]0`), // exponent must not have leading zeros + re(`e-[1-6]$`), // not tiny enough for exponential notation + re(`e+(.|1.|20)$`), // not big enough for exponential notation + re(`^-?0\.0000000`), // too tiny, should use exponential notation + re(`^-?[0-9]{22}`), // too big, should use exponential notation + re(`[1-9][0-9]{16}[1-9]`), // too many significant digits in integer + re(`[1-9][0-9.]{17}[1-9]`), // too many significant digits in decimal + // below here for float32 only + re(`[1-9][0-9]{8}[1-9]`), // too many significant digits in integer + re(`[1-9][0-9.]{9}[1-9]`), // too many significant digits in decimal +} + +func TestMarshalFloat(t *testing.T) { + t.Parallel() + nfail := 0 + test := func(f float64, bits int) { + vf := any(f) + if bits == 32 { + f = float64(float32(f)) // round + vf = float32(f) + } + bout, err := Marshal(vf) + if err != nil { + t.Errorf("Marshal(%T(%g)): %v", vf, vf, err) + nfail++ + return + } + out := string(bout) + + // result must convert back to the same float + g, err := strconv.ParseFloat(out, bits) + if err != nil { + t.Errorf("Marshal(%T(%g)) = %q, cannot parse back: %v", vf, vf, out, err) + nfail++ + return + } + if f != g || fmt.Sprint(f) != fmt.Sprint(g) { // fmt.Sprint handles ±0 + t.Errorf("Marshal(%T(%g)) = %q (is %g, not %g)", vf, vf, out, float32(g), vf) + nfail++ + return + } + + bad := badFloatREs + if bits == 64 { + bad = bad[:len(bad)-2] + } + for _, re := range bad { + if re.MatchString(out) { + t.Errorf("Marshal(%T(%g)) = %q, must not match /%s/", vf, vf, out, re) + nfail++ + return + } + } + } + + var ( + bigger = math.Inf(+1) + smaller = math.Inf(-1) + ) + + var digits = "1.2345678901234567890123" + for i := len(digits); i >= 2; i-- { + if testing.Short() && i < len(digits)-4 { + break + } + for exp := -30; exp <= 30; exp++ { + for _, sign := range "+-" { + for bits := 32; bits <= 64; bits += 32 { + s := fmt.Sprintf("%c%se%d", sign, digits[:i], exp) + f, err := strconv.ParseFloat(s, bits) + if err != nil { + log.Fatal(err) + } + next := math.Nextafter + if bits == 32 { + next = func(g, h float64) float64 { + return float64(math.Nextafter32(float32(g), float32(h))) + } + } + test(f, bits) + test(next(f, bigger), bits) + test(next(f, smaller), bits) + if nfail > 50 { + t.Fatalf("stopping test early") + } + } + } + } + } + test(0, 64) + test(math.Copysign(0, -1), 64) + test(0, 32) + test(math.Copysign(0, -1), 32) +} + +func TestMarshalRawMessageValue(t *testing.T) { + type ( + T1 struct { + M RawMessage `json:",omitempty"` + } + T2 struct { + M *RawMessage `json:",omitempty"` + } + ) + + var ( + rawNil = RawMessage(nil) + rawEmpty = RawMessage([]byte{}) + rawText = RawMessage([]byte(`"foo"`)) + ) + + tests := []struct { + in any + want string + ok bool + }{ + // Test with nil RawMessage. + {rawNil, "null", true}, + {&rawNil, "null", true}, + {[]any{rawNil}, "[null]", true}, + {&[]any{rawNil}, "[null]", true}, + {[]any{&rawNil}, "[null]", true}, + {&[]any{&rawNil}, "[null]", true}, + {struct{ M RawMessage }{rawNil}, `{"M":null}`, true}, + {&struct{ M RawMessage }{rawNil}, `{"M":null}`, true}, + {struct{ M *RawMessage }{&rawNil}, `{"M":null}`, true}, + {&struct{ M *RawMessage }{&rawNil}, `{"M":null}`, true}, + {map[string]any{"M": rawNil}, `{"M":null}`, true}, + {&map[string]any{"M": rawNil}, `{"M":null}`, true}, + {map[string]any{"M": &rawNil}, `{"M":null}`, true}, + {&map[string]any{"M": &rawNil}, `{"M":null}`, true}, + {T1{rawNil}, "{}", true}, + {T2{&rawNil}, `{"M":null}`, true}, + {&T1{rawNil}, "{}", true}, + {&T2{&rawNil}, `{"M":null}`, true}, + + // Test with empty, but non-nil, RawMessage. + {rawEmpty, "", false}, + {&rawEmpty, "", false}, + {[]any{rawEmpty}, "", false}, + {&[]any{rawEmpty}, "", false}, + {[]any{&rawEmpty}, "", false}, + {&[]any{&rawEmpty}, "", false}, + {struct{ X RawMessage }{rawEmpty}, "", false}, + {&struct{ X RawMessage }{rawEmpty}, "", false}, + {struct{ X *RawMessage }{&rawEmpty}, "", false}, + {&struct{ X *RawMessage }{&rawEmpty}, "", false}, + {map[string]any{"nil": rawEmpty}, "", false}, + {&map[string]any{"nil": rawEmpty}, "", false}, + {map[string]any{"nil": &rawEmpty}, "", false}, + {&map[string]any{"nil": &rawEmpty}, "", false}, + {T1{rawEmpty}, "{}", true}, + {T2{&rawEmpty}, "", false}, + {&T1{rawEmpty}, "{}", true}, + {&T2{&rawEmpty}, "", false}, + + // Test with RawMessage with some text. + // + // The tests below marked with Issue6458 used to generate "ImZvbyI=" instead "foo". + // This behavior was intentionally changed in Go 1.8. + // See https://golang.org/issues/14493#issuecomment-255857318 + {rawText, `"foo"`, true}, // Issue6458 + {&rawText, `"foo"`, true}, + {[]any{rawText}, `["foo"]`, true}, // Issue6458 + {&[]any{rawText}, `["foo"]`, true}, // Issue6458 + {[]any{&rawText}, `["foo"]`, true}, + {&[]any{&rawText}, `["foo"]`, true}, + {struct{ M RawMessage }{rawText}, `{"M":"foo"}`, true}, // Issue6458 + {&struct{ M RawMessage }{rawText}, `{"M":"foo"}`, true}, + {struct{ M *RawMessage }{&rawText}, `{"M":"foo"}`, true}, + {&struct{ M *RawMessage }{&rawText}, `{"M":"foo"}`, true}, + {map[string]any{"M": rawText}, `{"M":"foo"}`, true}, // Issue6458 + {&map[string]any{"M": rawText}, `{"M":"foo"}`, true}, // Issue6458 + {map[string]any{"M": &rawText}, `{"M":"foo"}`, true}, + {&map[string]any{"M": &rawText}, `{"M":"foo"}`, true}, + {T1{rawText}, `{"M":"foo"}`, true}, // Issue6458 + {T2{&rawText}, `{"M":"foo"}`, true}, + {&T1{rawText}, `{"M":"foo"}`, true}, + {&T2{&rawText}, `{"M":"foo"}`, true}, + } + + for i, tt := range tests { + b, err := Marshal(tt.in) + if ok := (err == nil); ok != tt.ok { + if err != nil { + t.Errorf("test %d, unexpected failure: %v", i, err) + } else { + t.Skip() // MODIFIED + t.Errorf("test %d, unexpected success", i) + } + } + if got := string(b); got != tt.want { + t.Errorf("test %d, Marshal(%#v) = %q, want %q", i, tt.in, got, tt.want) + } + } +} + +type marshalPanic struct{} + +func (marshalPanic) MarshalJSON() ([]byte, error) { panic(0xdead) } + +func TestMarshalPanic(t *testing.T) { + defer func() { + if got := recover(); !reflect.DeepEqual(got, 0xdead) { + t.Errorf("panic() = (%T)(%v), want 0xdead", got, got) + } + }() + _, _ = Marshal(&marshalPanic{}) // MODIFIED + t.Error("Marshal should have panicked") +} + +func TestMarshalUncommonFieldNames(t *testing.T) { + v := struct { + A0, À, Aβ int + }{} + b, err := Marshal(v) + if err != nil { + t.Fatal("Marshal:", err) + } + want := `{"A0":0,"À":0,"Aβ":0}` + got := string(b) + if got != want { + t.Fatalf("Marshal: got %s want %s", got, want) + } +} + +/* // MODIFIED +func TestMarshalerError(t *testing.T) { + s := "test variable" + st := reflect.TypeOf(s) + errText := "json: test error" + + tests := []struct { + err *MarshalerError + want string + }{ + { + &MarshalerError{st, fmt.Errorf(errText), ""}, + "json: error calling MarshalJSON for type " + st.String() + ": " + errText, + }, + { + &MarshalerError{st, fmt.Errorf(errText), "TestMarshalerError"}, + "json: error calling TestMarshalerError for type " + st.String() + ": " + errText, + }, + } + + for i, tt := range tests { + got := tt.err.Error() + if got != tt.want { + t.Errorf("MarshalerError test %d, got: %s, want: %s", i, got, tt.want) + } + } +} +*/ // MODIFIED diff --git a/borrowed_fuzz_test.go b/borrowed_fuzz_test.go new file mode 100644 index 0000000..21756a7 --- /dev/null +++ b/borrowed_fuzz_test.go @@ -0,0 +1,83 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package lowmemjson // MODIFIED + +import ( + "testing" +) + +func FuzzUnmarshalJSON(f *testing.F) { + f.Add([]byte(`{ +"object": { + "slice": [ + 1, + 2.0, + "3", + [4], + {5: {}} + ] +}, +"slice": [[]], +"string": ":)", +"int": 1e5, +"float": 3e-9" +}`)) + + f.Fuzz(func(t *testing.T, b []byte) { + for _, typ := range []func() interface{}{ + func() interface{} { return new(interface{}) }, + func() interface{} { return new(map[string]interface{}) }, + func() interface{} { return new([]interface{}) }, + } { + i := typ() + if err := Unmarshal(b, i); err != nil { + return + } + + encoded, err := Marshal(i) + if err != nil { + t.Fatalf("failed to marshal: %s", err) + } + + if err := Unmarshal(encoded, i); err != nil { + t.Fatalf("failed to roundtrip: %s", err) + } + } + }) +} + +/* // MODIFIED +func FuzzDecoderToken(f *testing.F) { + f.Add([]byte(`{ +"object": { + "slice": [ + 1, + 2.0, + "3", + [4], + {5: {}} + ] +}, +"slice": [[]], +"string": ":)", +"int": 1e5, +"float": 3e-9" +}`)) + + f.Fuzz(func(t *testing.T, b []byte) { + r := bytes.NewReader(b) + d := NewDecoder(r) + for { + _, err := d.Token() + if err != nil { + if err == io.EOF { + break + } + return + } + } + }) +} +*/ // MODIFIED diff --git a/borrowed_misc.go b/borrowed_misc.go new file mode 100644 index 0000000..5c6bbb6 --- /dev/null +++ b/borrowed_misc.go @@ -0,0 +1,42 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package lowmemjson + +import ( + "fmt" + "reflect" +) + +// A SyntaxError is a description of a JSON syntax error. +// +// from scanner.go +type SyntaxError struct { + msg string // description of error + Offset int64 // error occurred after reading Offset bytes +} + +func (e *SyntaxError) Error() string { + return fmt.Sprintf("JSON syntax error at input byte %v: %v", + e.Offset, e.msg) +} + +// from encode.go +func isEmptyValue(v reflect.Value) bool { + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Pointer: + return v.IsNil() + } + return false +} diff --git a/borrowed_scanner_test.go b/borrowed_scanner_test.go new file mode 100644 index 0000000..c5d67e6 --- /dev/null +++ b/borrowed_scanner_test.go @@ -0,0 +1,305 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package lowmemjson // MODIFIED + +import ( + "bytes" + "math" + "math/rand" + "reflect" + "testing" +) + +var validTests = []struct { + data string + ok bool +}{ + {`foo`, false}, + {`}{`, false}, + {`{]`, false}, + {`{}`, true}, + {`{"foo":"bar"}`, true}, + {`{"foo":"bar","bar":{"baz":["qux"]}}`, true}, +} + +func TestValid(t *testing.T) { + for _, tt := range validTests { + if ok := Valid([]byte(tt.data)); ok != tt.ok { + t.Errorf("Valid(%#q) = %v, want %v", tt.data, ok, tt.ok) + } + } +} + +// Tests of simple examples. + +type example struct { + compact string + indent string +} + +var examples = []example{ + {`1`, `1`}, + {`{}`, `{}`}, + {`[]`, `[]`}, + {`{"":2}`, "{\n\t\"\": 2\n}"}, + {`[3]`, "[\n\t3\n]"}, + {`[1,2,3]`, "[\n\t1,\n\t2,\n\t3\n]"}, + {`{"x":1}`, "{\n\t\"x\": 1\n}"}, + {ex1, ex1i}, + {"{\"\":\"<>&\u2028\u2029\"}", "{\n\t\"\": \"<>&\u2028\u2029\"\n}"}, // See golang.org/issue/34070 +} + +var ex1 = `[true,false,null,"x",1,1.5,0,-5e+2]` + +var ex1i = `[ + true, + false, + null, + "x", + 1, + 1.5, + 0, + -5e+2 +]` + +func TestCompact(t *testing.T) { + t.Skip() // TODO + var buf bytes.Buffer + for _, tt := range examples { + buf.Reset() + if err := Compact(&buf, []byte(tt.compact)); err != nil { + t.Errorf("Compact(%#q): %v", tt.compact, err) + } else if s := buf.String(); s != tt.compact { + t.Errorf("Compact(%#q) = %#q, want original", tt.compact, s) + } + + buf.Reset() + if err := Compact(&buf, []byte(tt.indent)); err != nil { + t.Errorf("Compact(%#q): %v", tt.indent, err) + continue + } else if s := buf.String(); s != tt.compact { + t.Errorf("Compact(%#q) = %#q, want %#q", tt.indent, s, tt.compact) + } + } +} + +func TestCompactSeparators(t *testing.T) { + t.Skip() // TODO + // U+2028 and U+2029 should be escaped inside strings. + // They should not appear outside strings. + tests := []struct { + in, compact string + }{ + {"{\"\u2028\": 1}", "{\"\u2028\":1}"}, + {"{\"\u2029\" :2}", "{\"\u2029\":2}"}, + } + for _, tt := range tests { + var buf bytes.Buffer + if err := Compact(&buf, []byte(tt.in)); err != nil { + t.Errorf("Compact(%q): %v", tt.in, err) + } else if s := buf.String(); s != tt.compact { + t.Errorf("Compact(%q) = %q, want %q", tt.in, s, tt.compact) + } + } +} + +func TestIndent(t *testing.T) { + t.Skip() // TODO + var buf bytes.Buffer + for _, tt := range examples { + buf.Reset() + if err := Indent(&buf, []byte(tt.indent), "", "\t"); err != nil { + t.Errorf("Indent(%#q): %v", tt.indent, err) + } else if s := buf.String(); s != tt.indent { + t.Errorf("Indent(%#q) = %#q, want original", tt.indent, s) + } + + buf.Reset() + if err := Indent(&buf, []byte(tt.compact), "", "\t"); err != nil { + t.Errorf("Indent(%#q): %v", tt.compact, err) + continue + } else if s := buf.String(); s != tt.indent { + t.Errorf("Indent(%#q) = %#q, want %#q", tt.compact, s, tt.indent) + } + } +} + +// Tests of a large random structure. + +func TestCompactBig(t *testing.T) { + initBig() + var buf bytes.Buffer + if err := Compact(&buf, jsonBig); err != nil { + t.Fatalf("Compact: %v", err) + } + b := buf.Bytes() + if !bytes.Equal(b, jsonBig) { + t.Error("Compact(jsonBig) != jsonBig") + diff(t, b, jsonBig) + return + } +} + +func TestIndentBig(t *testing.T) { + t.Parallel() + initBig() + var buf bytes.Buffer + if err := Indent(&buf, jsonBig, "", "\t"); err != nil { + t.Fatalf("Indent1: %v", err) + } + b := buf.Bytes() + if len(b) == len(jsonBig) { + // jsonBig is compact (no unnecessary spaces); + // indenting should make it bigger + t.Fatalf("Indent(jsonBig) did not get bigger") + } + + // should be idempotent + var buf1 bytes.Buffer + if err := Indent(&buf1, b, "", "\t"); err != nil { + t.Fatalf("Indent2: %v", err) + } + b1 := buf1.Bytes() + if !bytes.Equal(b1, b) { + t.Error("Indent(Indent(jsonBig)) != Indent(jsonBig)") + diff(t, b1, b) + return + } + + // should get back to original + buf1.Reset() + if err := Compact(&buf1, b); err != nil { + t.Fatalf("Compact: %v", err) + } + b1 = buf1.Bytes() + if !bytes.Equal(b1, jsonBig) { + t.Error("Compact(Indent(jsonBig)) != jsonBig") + diff(t, b1, jsonBig) + return + } +} + +type indentErrorTest struct { + in string + err error +} + +var indentErrorTests = []indentErrorTest{ + {`{"X": "foo", "Y"}`, &SyntaxError{"invalid character '}' after object key", 17}}, + {`{"X": "foo" "Y": "bar"}`, &SyntaxError{"invalid character '\"' after object key:value pair", 13}}, +} + +func TestIndentErrors(t *testing.T) { + t.Skip() // TODO + for i, tt := range indentErrorTests { + slice := make([]uint8, 0) + buf := bytes.NewBuffer(slice) + if err := Indent(buf, []uint8(tt.in), "", ""); err != nil { + if !reflect.DeepEqual(err, tt.err) { + t.Errorf("#%d: Indent: %#v", i, err) + continue + } + } + } +} + +func diff(t *testing.T, a, b []byte) { + for i := 0; ; i++ { + if i >= len(a) || i >= len(b) || a[i] != b[i] { + j := i - 10 + if j < 0 { + j = 0 + } + t.Errorf("diverge at %d: «%s» vs «%s»", i, trim(a[j:]), trim(b[j:])) + return + } + } +} + +func trim(b []byte) []byte { + if len(b) > 20 { + return b[0:20] + } + return b +} + +// Generate a random JSON object. + +var jsonBig []byte + +func initBig() { + n := 10000 + if testing.Short() { + n = 100 + } + b, err := Marshal(genValue(n)) + if err != nil { + panic(err) + } + jsonBig = b +} + +func genValue(n int) any { + if n > 1 { + switch rand.Intn(2) { + case 0: + return genArray(n) + case 1: + return genMap(n) + } + } + switch rand.Intn(3) { + case 0: + return rand.Intn(2) == 0 + case 1: + return rand.NormFloat64() + case 2: + return genString(30) + } + panic("unreachable") +} + +func genString(stddev float64) string { + n := int(math.Abs(rand.NormFloat64()*stddev + stddev/2)) + c := make([]rune, n) + for i := range c { + f := math.Abs(rand.NormFloat64()*64 + 32) + if f > 0x10ffff { + f = 0x10ffff + } + c[i] = rune(f) + } + return string(c) +} + +func genArray(n int) []any { + f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2))) + if f > n { + f = n + } + if f < 1 { + f = 1 + } + x := make([]any, f) + for i := range x { + x[i] = genValue(((i+1)*n)/f - (i*n)/f) + } + return x +} + +func genMap(n int) map[string]any { + f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2))) + if f > n { + f = n + } + if n > 0 && f == 0 { + f = 1 + } + x := make(map[string]any) + for i := 0; i < f; i++ { + x[genString(10)] = genValue(((i+1)*n)/f - (i*n)/f) + } + return x +} diff --git a/borrowed_tagkey_test.go b/borrowed_tagkey_test.go new file mode 100644 index 0000000..2d4d2c0 --- /dev/null +++ b/borrowed_tagkey_test.go @@ -0,0 +1,121 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package lowmemjson // MODIFIED + +import ( + "testing" +) + +type basicLatin2xTag struct { + V string `json:"$%-/"` +} + +type basicLatin3xTag struct { + V string `json:"0123456789"` +} + +type basicLatin4xTag struct { + V string `json:"ABCDEFGHIJKLMO"` +} + +type basicLatin5xTag struct { + V string `json:"PQRSTUVWXYZ_"` +} + +type basicLatin6xTag struct { + V string `json:"abcdefghijklmno"` +} + +type basicLatin7xTag struct { + V string `json:"pqrstuvwxyz"` +} + +type miscPlaneTag struct { + V string `json:"色は匂へど"` +} + +type percentSlashTag struct { + V string `json:"text/html%"` // https://golang.org/issue/2718 +} + +type punctuationTag struct { + V string `json:"!#$%&()*+-./:;<=>?@[]^_{|}~ "` // https://golang.org/issue/3546 +} + +type dashTag struct { + V string `json:"-,"` +} + +type emptyTag struct { + W string +} + +type misnamedTag struct { + X string `jsom:"Misnamed"` +} + +type badFormatTag struct { + Y string `:"BadFormat"` +} + +type badCodeTag struct { + Z string `json:" !\"#&'()*+,."` +} + +type spaceTag struct { + Q string `json:"With space"` +} + +type unicodeTag struct { + W string `json:"Ελλάδα"` +} + +var structTagObjectKeyTests = []struct { + raw any + value string + key string +}{ + {basicLatin2xTag{"2x"}, "2x", "$%-/"}, + {basicLatin3xTag{"3x"}, "3x", "0123456789"}, + {basicLatin4xTag{"4x"}, "4x", "ABCDEFGHIJKLMO"}, + {basicLatin5xTag{"5x"}, "5x", "PQRSTUVWXYZ_"}, + {basicLatin6xTag{"6x"}, "6x", "abcdefghijklmno"}, + {basicLatin7xTag{"7x"}, "7x", "pqrstuvwxyz"}, + {miscPlaneTag{"いろはにほへと"}, "いろはにほへと", "色は匂へど"}, + {dashTag{"foo"}, "foo", "-"}, + {emptyTag{"Pour Moi"}, "Pour Moi", "W"}, + {misnamedTag{"Animal Kingdom"}, "Animal Kingdom", "X"}, + {badFormatTag{"Orfevre"}, "Orfevre", "Y"}, + {badCodeTag{"Reliable Man"}, "Reliable Man", "Z"}, + {percentSlashTag{"brut"}, "brut", "text/html%"}, + {punctuationTag{"Union Rags"}, "Union Rags", "!#$%&()*+-./:;<=>?@[]^_{|}~ "}, + {spaceTag{"Perreddu"}, "Perreddu", "With space"}, + {unicodeTag{"Loukanikos"}, "Loukanikos", "Ελλάδα"}, +} + +func TestStructTagObjectKey(t *testing.T) { + t.Skip() // TODO + for _, tt := range structTagObjectKeyTests { + b, err := Marshal(tt.raw) + if err != nil { + t.Fatalf("Marshal(%#q) failed: %v", tt.raw, err) + } + var f any + err = Unmarshal(b, &f) + if err != nil { + t.Fatalf("Unmarshal(%#q) failed: %v", b, err) + } + for i, v := range f.(map[string]any) { + switch i { + case tt.key: + if s, ok := v.(string); !ok || s != tt.value { + t.Fatalf("Unexpected value: %#q, want %v", s, tt.value) + } + default: + t.Fatalf("Unexpected key: %#q, from %#q", i, b) + } + } + } +} diff --git a/borrowed_tags.go b/borrowed_tags.go new file mode 100644 index 0000000..07292b1 --- /dev/null +++ b/borrowed_tags.go @@ -0,0 +1,38 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package lowmemjson + +import ( + "strings" +) + +// tagOptions is the string following a comma in a struct field's "json" +// tag, or the empty string. It does not include the leading comma. +type tagOptions string + +// parseTag splits a struct field's json tag into its name and +// comma-separated options. +func parseTag(tag string) (string, tagOptions) { + tag, opt, _ := strings.Cut(tag, ",") + return tag, tagOptions(opt) +} + +// Contains reports whether a comma-separated list of options +// contains a particular substr flag. substr must be surrounded by a +// string boundary or commas. +func (o tagOptions) Contains(optionName string) bool { + if len(o) == 0 { + return false + } + s := string(o) + for s != "" { + var name string + name, s, _ = strings.Cut(s, ",") + if name == optionName { + return true + } + } + return false +} diff --git a/borrowed_tags_test.go b/borrowed_tags_test.go new file mode 100644 index 0000000..28d76e9 --- /dev/null +++ b/borrowed_tags_test.go @@ -0,0 +1,28 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package lowmemjson // MODIFIED + +import ( + "testing" +) + +func TestTagParsing(t *testing.T) { + name, opts := parseTag("field,foobar,foo") + if name != "field" { + t.Fatalf("name = %q, want field", name) + } + for _, tt := range []struct { + opt string + want bool + }{ + {"foobar", true}, + {"foo", true}, + {"bar", false}, + } { + if opts.Contains(tt.opt) != tt.want { + t.Errorf("Contains(%q) = %v", tt.opt, !tt.want) + } + } +} diff --git a/decode.go b/decode.go new file mode 100644 index 0000000..03d5b7a --- /dev/null +++ b/decode.go @@ -0,0 +1,812 @@ +// Copyright (C) 2022 Luke Shumaker +// +// SPDX-License-Identifier: GPL-2.0-or-later + +package lowmemjson + +import ( + "bufio" + "bytes" + "encoding" + "encoding/json" + "fmt" + "io" + "reflect" + "strconv" + "strings" +) + +type Decodable interface { + DecodeJSON(io.RuneScanner) error +} + +type runeBuffer interface { + io.Writer + WriteRune(rune) (int, error) + Reset() +} + +type Decoder struct { + r io.RuneScanner + + // config + disallowUnknownFields bool + useNumber bool + + // state + err error + curPos int64 + nxtPos int64 + stack []any +} + +var forceBufio bool + +func NewDecoder(r io.Reader) *Decoder { + rs, ok := r.(io.RuneScanner) + if forceBufio || !ok { + rs = bufio.NewReader(r) + } + return &Decoder{ + r: rs, + } +} + +func (dec *Decoder) DisallowUnknownFields() { dec.disallowUnknownFields = true } +func (dec *Decoder) UseNumber() { dec.useNumber = true } +func (dec *Decoder) InputOffset() int64 { return dec.curPos } + +func (dec *Decoder) More() bool { + dec.decodeWS() + _, ok := dec.peekRuneOrEOF() + return ok +} + +func (dec *Decoder) stackStr() string { + var buf strings.Builder + buf.WriteString("v") + for _, item := range dec.stack { + fmt.Fprintf(&buf, "[%#v]", item) + } + return buf.String() +} + +func (dec *Decoder) stackPush(idx any) { + dec.stack = append(dec.stack, idx) +} +func (dec *Decoder) stackPop() { + dec.stack = dec.stack[:len(dec.stack)-1] +} + +type decodeError struct { + Err error +} + +func (dec *Decoder) panicIO(err error) { + panic(decodeError{fmt.Errorf("json: I/O error at input byte %v: %s: %w", + dec.nxtPos, dec.stackStr(), err)}) +} +func (dec *Decoder) panicSyntax(err error) { + panic(decodeError{fmt.Errorf("json: syntax error at input byte %v: %s: %w", + dec.curPos, dec.stackStr(), err)}) +} +func (dec *Decoder) panicType(typ reflect.Type, err error) { + panic(decodeError{fmt.Errorf("json: type mismatch error at input byte %v: %s: type %v: %w", + dec.curPos, dec.stackStr(), typ, err)}) +} + +func Decode(r io.Reader, ptr any) error { + return NewDecoder(r).Decode(ptr) +} + +func (dec *Decoder) Decode(ptr any) (err error) { + ptrVal := reflect.ValueOf(ptr) + if ptrVal.Kind() != reflect.Pointer || ptrVal.IsNil() || !ptrVal.Elem().CanSet() { + return &json.InvalidUnmarshalError{ + // don't use ptrVal.Type() because ptrVal might be invalid if ptr==nil + Type: reflect.TypeOf(ptr), + } + } + + if dec.err != nil { + return dec.err + } + + defer func() { + if r := recover(); r != nil { + if de, ok := r.(decodeError); ok { + dec.err = de.Err + err = dec.err + } else { + panic(r) + } + } + }() + dec.decodeWS() + dec.decode(ptrVal.Elem(), false) + return nil +} + +func (dec *Decoder) readRune() rune { + c, size, err := dec.r.ReadRune() + if err != nil { + if err == io.EOF { + dec.panicSyntax(io.ErrUnexpectedEOF) + } + dec.panicIO(err) + } + dec.curPos = dec.nxtPos + dec.nxtPos = dec.curPos + int64(size) + return c +} + +func (dec *Decoder) readRuneOrEOF() (c rune, ok bool) { + c, size, err := dec.r.ReadRune() + if err != nil { + if err == io.EOF { + return 0, false + } + dec.panicIO(err) + } + dec.curPos = dec.nxtPos + dec.nxtPos = dec.curPos + int64(size) + return c, true +} + +func (dec *Decoder) unreadRune() { + if err := dec.r.UnreadRune(); err != nil { + // .UnreadRune() must succeed if the previous call was + // .ReadRune(), which it always is for this code. + panic(err) + } + dec.nxtPos = dec.curPos +} + +func (dec *Decoder) peekRune() rune { + c, _, err := dec.r.ReadRune() + if err != nil { + if err == io.EOF { + dec.panicSyntax(io.ErrUnexpectedEOF) + } + dec.panicIO(err) + } + if err := dec.r.UnreadRune(); err != nil { + // .UnreadRune() must succeed if the previous call was + // .ReadRune(), which it always is for this code. + panic(err) + } + return c +} + +func (dec *Decoder) peekRuneOrEOF() (rune, bool) { + c, _, err := dec.r.ReadRune() + if err != nil { + if err == io.EOF { + return 0, false + } + dec.panicIO(err) + } + if err := dec.r.UnreadRune(); err != nil { + // .UnreadRune() must succeed if the previous call was + // .ReadRune(), which it always is for this code. + panic(err) + } + return c, true +} + +func (dec *Decoder) expectRune(exp rune) { + act := dec.readRune() + if act != exp { + dec.panicSyntax(fmt.Errorf("expected %q but got %q", exp, act)) + } +} + +var ( + rawMessagePtrType = reflect.TypeOf((*json.RawMessage)(nil)) + decodableType = reflect.TypeOf((*Decodable)(nil)).Elem() + jsonUnmarshalerType = reflect.TypeOf((*json.Unmarshaler)(nil)).Elem() + textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() +) + +var kind2bits = map[reflect.Kind]int{ + reflect.Int: int(32 << (^uint(0) >> 63)), + reflect.Int8: 8, + reflect.Int16: 16, + reflect.Int32: 32, + reflect.Int64: 64, + + reflect.Uint: int(32 << (^uint(0) >> 63)), + reflect.Uint8: 8, + reflect.Uint16: 16, + reflect.Uint32: 32, + reflect.Uint64: 64, + + reflect.Uintptr: int(32 << (^uintptr(0) >> 63)), + + reflect.Float32: 32, + reflect.Float64: 64, +} + +func (dec *Decoder) decode(val reflect.Value, nullOK bool) { + typ := val.Type() + switch { + case val.CanAddr() && reflect.PointerTo(typ) == rawMessagePtrType: + var buf bytes.Buffer + dec.scan(&buf) + if err := val.Addr().Interface().(*json.RawMessage).UnmarshalJSON(buf.Bytes()); err != nil { + dec.panicSyntax(err) + } + case val.CanAddr() && reflect.PointerTo(typ).Implements(decodableType): + obj := val.Addr().Interface().(Decodable) + if err := obj.DecodeJSON(dec.r); err != nil { + dec.panicSyntax(err) + } + case val.CanAddr() && reflect.PointerTo(typ).Implements(jsonUnmarshalerType): + var buf bytes.Buffer + dec.scan(&buf) + obj := val.Addr().Interface().(json.Unmarshaler) + if err := obj.UnmarshalJSON(buf.Bytes()); err != nil { + dec.panicSyntax(err) + } + case val.CanAddr() && reflect.PointerTo(typ).Implements(textUnmarshalerType): + if nullOK && dec.peekRune() == 'n' { + dec.decodeNull() + return + } + var buf bytes.Buffer + dec.decodeString(&buf) + obj := val.Addr().Interface().(encoding.TextUnmarshaler) + if err := obj.UnmarshalText(buf.Bytes()); err != nil { + dec.panicSyntax(err) + } + default: + kind := typ.Kind() + switch kind { + case reflect.Bool: + if nullOK && dec.peekRune() == 'n' { + dec.decodeNull() + return + } + val.SetBool(dec.decodeBool()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if nullOK && dec.peekRune() == 'n' { + dec.decodeNull() + return + } + var buf strings.Builder + dec.scanNumber(&buf) + n, err := strconv.ParseInt(buf.String(), 10, kind2bits[kind]) + if err != nil { + dec.panicSyntax(err) + } + val.SetInt(n) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + if nullOK && dec.peekRune() == 'n' { + dec.decodeNull() + return + } + var buf strings.Builder + dec.scanNumber(&buf) + n, err := strconv.ParseUint(buf.String(), 10, kind2bits[kind]) + if err != nil { + dec.panicSyntax(err) + } + val.SetUint(n) + case reflect.Float32, reflect.Float64: + if nullOK && dec.peekRune() == 'n' { + dec.decodeNull() + return + } + var buf strings.Builder + dec.scanNumber(&buf) + n, err := strconv.ParseFloat(buf.String(), kind2bits[kind]) + if err != nil { + dec.panicSyntax(err) + } + val.SetFloat(n) + case reflect.String: + if nullOK && dec.peekRune() == 'n' { + dec.decodeNull() + return + } + var buf strings.Builder + if typ == numberType { + dec.scanNumber(&buf) + val.SetString(buf.String()) + } else { + dec.decodeString(&buf) + val.SetString(buf.String()) + } + case reflect.Interface: + if typ.NumMethod() > 0 { + dec.panicType(typ, fmt.Errorf("cannot decode in to non-empty interface")) + } + switch dec.peekRune() { + case 'n': + if !val.IsNil() && val.Elem().Kind() == reflect.Pointer && val.Elem().Elem().Kind() == reflect.Pointer { + // XXX: I can't justify this case, other than "it's what encoding/json does, but + // I don't understand their rationale". + dec.decode(val.Elem(), false) + } else { + dec.decodeNull() + val.Set(reflect.Zero(typ)) + } + default: + if !val.IsNil() && val.Elem().Kind() == reflect.Pointer { + dec.decode(val.Elem(), false) + } else { + val.Set(reflect.ValueOf(dec.decodeAny())) + } + } + case reflect.Struct: + if nullOK && dec.peekRune() == 'n' { + dec.decodeNull() + return + } + index := indexStruct(typ) + var nameBuf strings.Builder + dec.decodeObject(&nameBuf, func() { + name := nameBuf.String() + dec.stackPush(name) + defer dec.stackPop() + idx, ok := index.byName[name] + if !ok { + if dec.disallowUnknownFields { + dec.panicType(typ, fmt.Errorf("unknown field %q", name)) + } + dec.scan(io.Discard) + return + } + field := index.byPos[idx] + fVal := val + for _, idx := range field.Path { + if fVal.Kind() == reflect.Pointer { + if fVal.IsNil() { + if !fVal.CanSet() { // https://golang.org/issue/21357 + dec.panicType(fVal.Type().Elem(), fmt.Errorf("cannot set embedded pointer to unexported type")) + } + fVal.Set(reflect.New(fVal.Type().Elem())) + } + fVal = fVal.Elem() + } + fVal = fVal.Field(idx) + } + if field.Quote { + switch dec.peekRune() { + case 'n': + dec.decodeNull() + switch fVal.Kind() { + // XXX: I can't justify this list, other than "it's what encoding/json + // does, but I don't understand their rationale". + case reflect.Interface, reflect.Pointer, reflect.Map, reflect.Slice: + fVal.Set(reflect.Zero(fVal.Type())) + } + case '"': + // TODO: Figure out how to do this without buffering. + var buf bytes.Buffer + subD := *dec // capture the .curPos *before* calling .decodeString + dec.decodeString(&buf) + subD.r = &buf + subD.decode(fVal, false) + default: + dec.panicSyntax(fmt.Errorf(",string field: expected %q or %q but got %q", + 'n', '"', dec.peekRune())) + } + } else { + dec.decode(fVal, true) + } + }) + case reflect.Map: + switch dec.peekRune() { + case 'n': + dec.decodeNull() + val.Set(reflect.Zero(typ)) + case '{': + if val.IsNil() { + val.Set(reflect.MakeMap(typ)) + } + var nameBuf bytes.Buffer + dec.decodeObject(&nameBuf, func() { + nameValTyp := typ.Key() + nameValPtr := reflect.New(nameValTyp) + switch { + case reflect.PointerTo(nameValTyp).Implements(textUnmarshalerType): + obj := nameValPtr.Interface().(encoding.TextUnmarshaler) + if err := obj.UnmarshalText(nameBuf.Bytes()); err != nil { + dec.panicSyntax(err) + } + default: + switch nameValTyp.Kind() { + case reflect.String: + nameValPtr.Elem().SetString(nameBuf.String()) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + n, err := strconv.ParseInt(nameBuf.String(), 10, kind2bits[nameValTyp.Kind()]) + if err != nil { + dec.panicSyntax(err) + } + nameValPtr.Elem().SetInt(n) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + n, err := strconv.ParseUint(nameBuf.String(), 10, kind2bits[nameValTyp.Kind()]) + if err != nil { + dec.panicSyntax(err) + } + nameValPtr.Elem().SetUint(n) + default: + dec.panicType(typ, fmt.Errorf("invalid map key type: %v", nameValTyp)) + } + } + dec.stackPush(nameValPtr.Elem()) + defer dec.stackPop() + + fValPtr := reflect.New(typ.Elem()) + dec.decode(fValPtr.Elem(), false) + + val.SetMapIndex(nameValPtr.Elem(), fValPtr.Elem()) + }) + default: + dec.panicSyntax(fmt.Errorf("map: expected %q or %q bug got %q", 'n', '{', dec.peekRune())) + } + case reflect.Slice: + switch { + case typ.Elem().Kind() == reflect.Uint8: + switch dec.peekRune() { + case 'n': + dec.decodeNull() + val.Set(reflect.Zero(typ)) + case '"': + var buf bytes.Buffer + dec.decodeString(newBase64Decoder(&buf)) + if typ.Elem() == byteType { + val.Set(reflect.ValueOf(buf.Bytes())) + } else { + bs := buf.Bytes() + // TODO: Surely there's a better way. + val.Set(reflect.MakeSlice(typ, len(bs), len(bs))) + for i := 0; i < len(bs); i++ { + val.Index(i).Set(reflect.ValueOf(bs[i]).Convert(typ.Elem())) + } + } + default: + dec.panicSyntax(fmt.Errorf("byte slice: expected %q or %q but got %q", 'n', '"', dec.peekRune())) + } + default: + switch dec.peekRune() { + case 'n': + dec.decodeNull() + val.Set(reflect.Zero(typ)) + case '[': + if val.IsNil() { + val.Set(reflect.MakeSlice(typ, 0, 0)) + } + if val.Len() > 0 { + val.Set(val.Slice(0, 0)) + } + i := 0 + dec.decodeArray(func() { + dec.stackPush(i) + defer dec.stackPop() + mValPtr := reflect.New(typ.Elem()) + dec.decode(mValPtr.Elem(), false) + val.Set(reflect.Append(val, mValPtr.Elem())) + i++ + }) + default: + dec.panicSyntax(fmt.Errorf("slice: expected %q or %q but got %q", 'n', '[', dec.peekRune())) + } + } + case reflect.Array: + if nullOK && dec.peekRune() == 'n' { + dec.decodeNull() + return + } + i := 0 + n := val.Len() + dec.decodeArray(func() { + dec.stackPush(i) + defer dec.stackPop() + if i < n { + mValPtr := reflect.New(typ.Elem()) + dec.decode(mValPtr.Elem(), false) + val.Index(i).Set(mValPtr.Elem()) + } else { + dec.scan(io.Discard) + } + i++ + }) + for ; i < n; i++ { + val.Index(i).Set(reflect.Zero(typ.Elem())) + } + case reflect.Pointer: + switch dec.peekRune() { + case 'n': + dec.decodeNull() + /* + for typ.Elem().Kind() == reflect.Pointer { + if val.IsNil() || !val.Elem().CanSet() { + val.Set(reflect.New(typ.Elem())) + } + val = val.Elem() + typ = val.Type() + } + */ + val.Set(reflect.Zero(typ)) + default: + if val.IsNil() { + val.Set(reflect.New(typ.Elem())) + } + dec.decode(val.Elem(), false) + } + default: + dec.panicType(typ, fmt.Errorf("unsupported type (kind=%v)", typ.Kind())) + } + } +} + +func (dec *Decoder) decodeWS() { + for { + c, ok := dec.readRuneOrEOF() + if !ok { + return + } + switch c { + // NB: The JSON definition of whitespace is more + // narrow than unicode.IsSpace + case 0x0020, 0x000A, 0x000D, 0x0009: + // do nothing + default: + dec.unreadRune() + return + } + } +} + +func (dec *Decoder) scan(out io.Writer) { + scanner := &ReEncoder{ + Out: out, + Compact: true, + } + if _, err := scanner.WriteRune(dec.readRune()); err != nil { + dec.panicSyntax(err) + } + scanner.bailAfterCurrent = true + var err error + var eof bool + for err == nil { + c, ok := dec.readRuneOrEOF() + if ok { + _, err = scanner.WriteRune(c) + } else { + eof = true + err = scanner.Flush() + break + } + } + if err != nil { + if err == errBailedAfterCurrent { + if !eof { + dec.unreadRune() + } + } else { + dec.panicSyntax(err) + } + } +} + +func (dec *Decoder) scanNumber(out io.Writer) { + c := dec.peekRune() + switch c { + case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + dec.scan(out) + default: + dec.panicSyntax(fmt.Errorf("number: expected %q or a digit, but got %q", '-', c)) + } +} + +func (dec *Decoder) decodeAny() any { + c := dec.peekRune() + switch c { + case '{': + ret := make(map[string]any) + var nameBuf strings.Builder + dec.decodeObject(&nameBuf, func() { + name := nameBuf.String() + dec.stackPush(name) + defer dec.stackPop() + ret[name] = dec.decodeAny() + }) + return ret + case '[': + ret := []any{} + dec.decodeArray(func() { + dec.stackPush(len(ret)) + defer dec.stackPop() + ret = append(ret, dec.decodeAny()) + }) + return ret + case '"': + var buf strings.Builder + dec.decodeString(&buf) + return buf.String() + case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + var buf strings.Builder + dec.scanNumber(&buf) + num := json.Number(buf.String()) + if dec.useNumber { + return num + } + f64, err := num.Float64() + if err != nil { + dec.panicSyntax(err) + } + return f64 + case 't', 'f': + return dec.decodeBool() + case 'n': + dec.decodeNull() + return nil + default: + dec.panicSyntax(fmt.Errorf("any: unexpected character: %c", c)) + panic("not reached") + } +} + +func (dec *Decoder) decodeObject(nameBuf runeBuffer, decodeKVal func()) { + dec.expectRune('{') + dec.decodeWS() + c := dec.readRune() + switch c { + case '"': + decodeMember: + dec.unreadRune() + nameBuf.Reset() + dec.decodeString(nameBuf) + dec.decodeWS() + dec.expectRune(':') + dec.decodeWS() + decodeKVal() + dec.decodeWS() + c := dec.readRune() + switch c { + case ',': + dec.decodeWS() + dec.expectRune('"') + goto decodeMember + case '}': + return + default: + dec.panicSyntax(fmt.Errorf("object: expected %q or %q but got %q", ',', '}', c)) + } + case '}': + return + default: + dec.panicSyntax(fmt.Errorf("object: expected %q or %q but got %q", '"', '}', c)) + } +} + +func (dec *Decoder) decodeArray(decodeMember func()) { + dec.expectRune('[') + dec.decodeWS() + c := dec.readRune() + switch c { + case ']': + return + default: + dec.unreadRune() + decodeNextMember: + decodeMember() + dec.decodeWS() + c := dec.readRune() + switch c { + case ',': + dec.decodeWS() + goto decodeNextMember + case ']': + return + default: + dec.panicSyntax(fmt.Errorf("array: expected %c or %c but got %c", ',', ']', c)) + } + } +} + +func (dec *Decoder) decodeHex() rune { + c := dec.readRune() + switch { + case '0' <= c && c <= '9': + return c - '0' + case 'a' <= c && c <= 'f': + return c - 'a' + 10 + case 'A' <= c && c <= 'F': + return c - 'A' + 10 + default: + dec.panicSyntax(fmt.Errorf("string: expected a hex digit but got %q", c)) + panic("not reached") + } +} + +func (dec *Decoder) decodeString(out io.Writer) { + dec.expectRune('"') + for { + c := dec.readRune() + switch { + case 0x0020 <= c && c <= 0x10FFFF && c != '"' && c != '\\': + if _, err := writeRune(out, c); err != nil { + dec.panicSyntax(err) + } + case c == '\\': + c = dec.readRune() + switch c { + case '"': + if _, err := writeRune(out, '"'); err != nil { + dec.panicSyntax(err) + } + case '\\': + if _, err := writeRune(out, '\\'); err != nil { + dec.panicSyntax(err) + } + case '/': + if _, err := writeRune(out, '/'); err != nil { + dec.panicSyntax(err) + } + case 'b': + if _, err := writeRune(out, '\b'); err != nil { + dec.panicSyntax(err) + } + case 'f': + if _, err := writeRune(out, '\f'); err != nil { + dec.panicSyntax(err) + } + case 'n': + if _, err := writeRune(out, '\n'); err != nil { + dec.panicSyntax(err) + } + case 'r': + if _, err := writeRune(out, '\r'); err != nil { + dec.panicSyntax(err) + } + case 't': + if _, err := writeRune(out, '\t'); err != nil { + dec.panicSyntax(err) + } + case 'u': + c = dec.decodeHex() + c = (c << 4) | dec.decodeHex() + c = (c << 4) | dec.decodeHex() + c = (c << 4) | dec.decodeHex() + if _, err := writeRune(out, c); err != nil { + dec.panicSyntax(err) + } + } + case c == '"': + return + default: + dec.panicSyntax(fmt.Errorf("string: unexpected %c", c)) + } + } +} + +func (dec *Decoder) decodeBool() bool { + c := dec.readRune() + switch c { + case 't': + dec.expectRune('r') + dec.expectRune('u') + dec.expectRune('e') + return true + case 'f': + dec.expectRune('a') + dec.expectRune('l') + dec.expectRune('s') + dec.expectRune('e') + return false + default: + dec.panicSyntax(fmt.Errorf("bool: expected %q or %q but got %q", 't', 'f', c)) + panic("not reached") + } +} + +func (dec *Decoder) decodeNull() { + dec.expectRune('n') + dec.expectRune('u') + dec.expectRune('l') + dec.expectRune('l') +} diff --git a/encode.go b/encode.go new file mode 100644 index 0000000..377b9b9 --- /dev/null +++ b/encode.go @@ -0,0 +1,333 @@ +// Copyright (C) 2022 Luke Shumaker +// +// SPDX-License-Identifier: GPL-2.0-or-later + +package lowmemjson + +import ( + "bytes" + "encoding" + "encoding/base64" + "encoding/json" + "io" + "reflect" + "sort" + "strconv" + "strings" +) + +type Encodable interface { + EncodeJSON(w io.Writer) error +} + +type encodeError struct { + Err error +} + +func encodeWriteByte(w io.Writer, b byte) { + if err := writeByte(w, b); err != nil { + panic(encodeError{err}) + } +} + +func encodeWriteString(w io.Writer, str string) { + if _, err := io.WriteString(w, str); err != nil { + panic(encodeError{err}) + } +} + +func Encode(w io.Writer, obj any) (err error) { + defer func() { + if r := recover(); r != nil { + if e, ok := r.(encodeError); ok { + err = e.Err + } else { + panic(r) + } + } + }() + encode(w, reflect.ValueOf(obj), false) + if f, ok := w.(interface{ Flush() error }); ok { + return f.Flush() + } + return nil +} + +var ( + encodableType = reflect.TypeOf((*Encodable)(nil)).Elem() + jsonMarshalerType = reflect.TypeOf((*json.Marshaler)(nil)).Elem() + textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem() +) + +func encode(w io.Writer, val reflect.Value, quote bool) { + if !val.IsValid() { + encodeWriteString(w, "null") + return + } + switch { + + case val.Kind() != reflect.Pointer && val.CanAddr() && reflect.PointerTo(val.Type()).Implements(encodableType): + val = val.Addr() + fallthrough + case val.Type().Implements(encodableType): + if val.Kind() == reflect.Pointer && val.IsNil() { + encodeWriteString(w, "null") + return + } + obj, ok := val.Interface().(Encodable) + if !ok { + encodeWriteString(w, "null") + return + } + if err := obj.EncodeJSON(w); err != nil { + panic(encodeError{err}) + } + + case val.Kind() != reflect.Pointer && val.CanAddr() && reflect.PointerTo(val.Type()).Implements(jsonMarshalerType): + val = val.Addr() + fallthrough + case val.Type().Implements(jsonMarshalerType): + if val.Kind() == reflect.Pointer && val.IsNil() { + encodeWriteString(w, "null") + return + } + obj, ok := val.Interface().(json.Marshaler) + if !ok { + encodeWriteString(w, "null") + return + } + dat, err := obj.MarshalJSON() + if err != nil { + panic(encodeError{err}) + } + if _, err := w.Write(dat); err != nil { + panic(encodeError{err}) + } + + case val.Kind() != reflect.Pointer && val.CanAddr() && reflect.PointerTo(val.Type()).Implements(textMarshalerType): + val = val.Addr() + fallthrough + case val.Type().Implements(textMarshalerType): + if val.Kind() == reflect.Pointer && val.IsNil() { + encodeWriteString(w, "null") + return + } + obj, ok := val.Interface().(encoding.TextMarshaler) + if !ok { + encodeWriteString(w, "null") + return + } + text, err := obj.MarshalText() + if err != nil { + panic(encodeError{err}) + } + encodeString(w, text) + + default: + switch val.Kind() { + case reflect.Bool: + if quote { + encodeWriteByte(w, '"') + } + if val.Bool() { + encodeWriteString(w, "true") + } else { + encodeWriteString(w, "false") + } + if quote { + encodeWriteByte(w, '"') + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if quote { + encodeWriteByte(w, '"') + } + encodeWriteString(w, strconv.FormatInt(val.Int(), 10)) + if quote { + encodeWriteByte(w, '"') + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + if quote { + encodeWriteByte(w, '"') + } + encodeWriteString(w, strconv.FormatUint(val.Uint(), 10)) + if quote { + encodeWriteByte(w, '"') + } + case reflect.Float32, reflect.Float64: + if quote { + encodeWriteByte(w, '"') + } + encodeTODO(w, val) + if quote { + encodeWriteByte(w, '"') + } + case reflect.String: + if val.Type() == numberType { + numStr := val.String() + if numStr == "" { + numStr = "0" + } + if quote { + encodeWriteByte(w, '"') + } + encodeWriteString(w, numStr) + if quote { + encodeWriteByte(w, '"') + } + } else { + if quote { + var buf bytes.Buffer + encodeString(&buf, val.String()) + encodeString(w, buf.Bytes()) + } else { + encodeString(w, val.String()) + } + } + case reflect.Interface: + if val.IsNil() { + encodeWriteString(w, "null") + } else { + encode(w, val.Elem(), quote) + } + case reflect.Struct: + encodeWriteByte(w, '{') + empty := true + for _, field := range indexStruct(val.Type()).byPos { + fVal, err := val.FieldByIndexErr(field.Path) + if err != nil { + continue + } + if field.OmitEmpty && isEmptyValue(fVal) { + continue + } + if !empty { + encodeWriteByte(w, ',') + } + empty = false + encodeString(w, field.Name) + encodeWriteByte(w, ':') + encode(w, fVal, field.Quote) + } + encodeWriteByte(w, '}') + case reflect.Map: + if val.IsNil() { + encodeWriteString(w, "null") + return + } + if val.Len() == 0 { + encodeWriteString(w, "{}") + return + } + encodeWriteByte(w, '{') + + type kv struct { + K string + V reflect.Value + } + kvs := make([]kv, val.Len()) + iter := val.MapRange() + for i := 0; iter.Next(); i++ { + var k strings.Builder + encode(&k, iter.Key(), false) + kStr := k.String() + if kStr == "null" { + kStr = `""` + } + if !strings.HasPrefix(kStr, `"`) { + k.Reset() + encodeString(&k, kStr) + kStr = k.String() + } + kvs[i].K = kStr + kvs[i].V = iter.Value() + } + sort.Slice(kvs, func(i, j int) bool { + return kvs[i].K < kvs[j].K + }) + + for i, kv := range kvs { + if i > 0 { + encodeWriteByte(w, ',') + } + encodeWriteString(w, kv.K) + encodeWriteByte(w, ':') + encode(w, kv.V, false) + } + encodeWriteByte(w, '}') + case reflect.Slice: + switch { + case val.IsNil(): + encodeWriteString(w, "null") + case val.Type().Elem().Kind() == reflect.Uint8: + encodeWriteByte(w, '"') + enc := base64.NewEncoder(base64.StdEncoding, w) + if val.CanConvert(byteSliceType) { + if _, err := enc.Write(val.Convert(byteSliceType).Interface().([]byte)); err != nil { + panic(encodeError{err}) + } + } else { + // TODO: Surely there's a better way. + for i, n := 0, val.Len(); i < n; i++ { + var buf [1]byte + buf[0] = val.Index(i).Convert(byteType).Interface().(byte) + if _, err := enc.Write(buf[:]); err != nil { + panic(encodeError{err}) + } + } + } + if err := enc.Close(); err != nil { + panic(encodeError{err}) + } + encodeWriteByte(w, '"') + default: + encodeArray(w, val) + } + case reflect.Array: + encodeArray(w, val) + case reflect.Pointer: + if val.IsNil() { + encodeWriteString(w, "null") + } else { + encode(w, val.Elem(), quote) + } + default: + panic(encodeError{&json.UnsupportedTypeError{ + Type: val.Type(), + }}) + } + } +} + +func encodeString[T interface{ []byte | string }](w io.Writer, str T) { + encodeWriteByte(w, '"') + for i := 0; i < len(str); { + c, size := decodeRune(str[i:]) + if _, err := writeStringChar(w, c, false, nil); err != nil { + panic(encodeError{err}) + } + i += size + } + encodeWriteByte(w, '"') +} + +func encodeArray(w io.Writer, val reflect.Value) { + encodeWriteByte(w, '[') + n := val.Len() + for i := 0; i < n; i++ { + if i > 0 { + encodeWriteByte(w, ',') + } + encode(w, val.Index(i), false) + } + encodeWriteByte(w, ']') +} + +func encodeTODO(w io.Writer, val reflect.Value) { + bs, err := json.Marshal(val.Interface()) + if err != nil { + panic(encodeError{err}) + } + if _, err := w.Write(bs); err != nil { + panic(encodeError{err}) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..74386a3 --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module git.lukeshu.com/go/lowmemjson + +go 1.18 + +require github.com/stretchr/testify v1.8.0 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..5164829 --- /dev/null +++ b/go.sum @@ -0,0 +1,15 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/lib/lowmemjson.stock/borrowed_tags_test.go b/lib/lowmemjson.stock/borrowed_tags_test.go deleted file mode 100644 index 8ba8ddd..0000000 --- a/lib/lowmemjson.stock/borrowed_tags_test.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package json - -import ( - "testing" -) - -func TestTagParsing(t *testing.T) { - name, opts := parseTag("field,foobar,foo") - if name != "field" { - t.Fatalf("name = %q, want field", name) - } - for _, tt := range []struct { - opt string - want bool - }{ - {"foobar", true}, - {"foo", true}, - {"bar", false}, - } { - if opts.Contains(tt.opt) != tt.want { - t.Errorf("Contains(%q) = %v", tt.opt, !tt.want) - } - } -} diff --git a/lib/lowmemjson/adapter_test.go b/lib/lowmemjson/adapter_test.go deleted file mode 100644 index b1aec3e..0000000 --- a/lib/lowmemjson/adapter_test.go +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright (C) 2022 Luke Shumaker -// -// SPDX-License-Identifier: GPL-2.0-or-later - -package lowmemjson - -import ( - "bufio" - "bytes" - "encoding/json" - "io" -) - -func MarshalIndent(v any, prefix, indent string) ([]byte, error) { - var buf bytes.Buffer - formatter := &ReEncoder{ - Out: &buf, - Indent: indent, - prefix: prefix, - } - err := Encode(formatter, v) - return buf.Bytes(), err -} - -func Marshal(v any) ([]byte, error) { - var buf bytes.Buffer - formatter := &ReEncoder{ - Out: &buf, - Compact: true, - } - err := Encode(formatter, v) - return buf.Bytes(), err -} - -func HTMLEscape(dst *bytes.Buffer, src []byte) { - formatter := &ReEncoder{ - Out: dst, - } - _, _ = formatter.Write(src) -} - -func Compact(dst *bytes.Buffer, src []byte) error { - formatter := &ReEncoder{ - Out: dst, - Compact: true, - } - _, err := formatter.Write(src) - return err -} - -func Indent(dst *bytes.Buffer, src []byte, prefix, indent string) error { - formatter := &ReEncoder{ - Out: dst, - Indent: indent, - prefix: prefix, - } - _, err := formatter.Write(src) - return err -} - -func Valid(data []byte) bool { - formatter := &ReEncoder{ - Out: io.Discard, - Compact: true, - } - _, err := formatter.Write(data) - return err == nil -} - -func Unmarshal(data []byte, ptr any) error { - return Decode(bytes.NewReader(data), ptr) -} - -func init() { - forceBufio = true -} - -func (dec *Decoder) Buffered() io.Reader { - buf := dec.r.(*bufio.Reader) - dat, _ := buf.Peek(buf.Buffered()) - return bytes.NewReader(dat) -} - -//func (dec *Decoder) Token() (Token, error) - -///////////////////////////////////////////////////////////////////// - -type ( - Number = json.Number - Marshaler = json.Marshaler - RawMessage = json.RawMessage - - UnsupportedValueError = json.UnsupportedValueError - MarshalerError = json.MarshalerError - UnmarshalTypeError = json.UnmarshalTypeError -) - -const ( - startDetectingCyclesAfter = 1000 -) - -func isSpace(c byte) bool { - switch c { - case 0x0020, 0x000A, 0x000D, 0x0009: - return true - default: - return false - } -} - -type encodeState struct { - bytes.Buffer -} - -func (es *encodeState) string(str string, _ bool) { - encodeString(&es.Buffer, str) -} -func (es *encodeState) stringBytes(str []byte, _ bool) { - encodeString(&es.Buffer, str) -} diff --git a/lib/lowmemjson/base64.go b/lib/lowmemjson/base64.go deleted file mode 100644 index 86fc293..0000000 --- a/lib/lowmemjson/base64.go +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (C) 2022 Luke Shumaker -// -// SPDX-License-Identifier: GPL-2.0-or-later - -package lowmemjson - -import ( - "encoding/base64" - "io" - "strings" -) - -type base64Decoder struct { - dst io.Writer - - err error - pos int64 - buf [4]byte - bufLen int -} - -func newBase64Decoder(w io.Writer) io.WriteCloser { - return &base64Decoder{ - dst: w, - } -} - -func (dec *base64Decoder) decodeByte(b byte) (byte, bool) { - const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" - n := strings.IndexByte(alphabet, b) - if n < 0 { - return 0, false - } - dec.pos++ - return byte(n), true -} - -func (dec *base64Decoder) decodeTuple(a, b, c, d byte) error { - var decodedLen int - var encoded [4]byte - var ok bool - - if a != '=' { - encoded[0], ok = dec.decodeByte(a) - if !ok { - return base64.CorruptInputError(dec.pos) - } - decodedLen++ - } - if b != '=' { - encoded[1], ok = dec.decodeByte(b) - if !ok { - return base64.CorruptInputError(dec.pos) - } - // do NOT increment decodedLen here - } - if c != '=' { - encoded[2], ok = dec.decodeByte(c) - if !ok { - return base64.CorruptInputError(dec.pos) - } - decodedLen++ - } - if d != '=' { - encoded[3], ok = dec.decodeByte(d) - if !ok { - return base64.CorruptInputError(dec.pos) - } - decodedLen++ - } - - val := 0 | - uint32(encoded[0])<<18 | - uint32(encoded[1])<<12 | - uint32(encoded[2])<<6 | - uint32(encoded[3])<<0 - var decoded [3]byte - decoded[0] = byte(val >> 16) - decoded[1] = byte(val >> 8) - decoded[2] = byte(val >> 0) - - _, err := dec.dst.Write(decoded[:decodedLen]) - return err -} - -func (dec *base64Decoder) Write(dat []byte) (int, error) { - if len(dat) == 0 { - return 0, nil - } - if dec.err != nil { - return 0, dec.err - } - var n int - if dec.bufLen > 0 { - n = copy(dec.buf[dec.bufLen:], dat) - dec.bufLen += n - if dec.bufLen < 4 { - return len(dat), nil - } - if err := dec.decodeTuple(dec.buf[0], dec.buf[1], dec.buf[2], dec.buf[3]); err != nil { - dec.err = err - return 0, dec.err - } - } - for ; n+3 < len(dat); n += 4 { - if err := dec.decodeTuple(dat[n], dat[n+1], dat[n+2], dat[n+3]); err != nil { - dec.err = err - return n, dec.err - } - } - dec.bufLen = copy(dec.buf[:], dat[n:]) - return len(dat), nil -} - -func (dec *base64Decoder) Close() error { - if dec.bufLen == 0 { - return nil - } - copy(dec.buf[:], "====") - return dec.decodeTuple(dec.buf[0], dec.buf[1], dec.buf[2], dec.buf[3]) -} diff --git a/lib/lowmemjson/base64_test.go b/lib/lowmemjson/base64_test.go deleted file mode 100644 index 43367af..0000000 --- a/lib/lowmemjson/base64_test.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (C) 2022 Luke Shumaker -// -// SPDX-License-Identifier: GPL-2.0-or-later - -package lowmemjson - -import ( - "bytes" - "encoding/base64" - "testing" - - "github.com/stretchr/testify/require" -) - -func b64encode(t *testing.T, input []byte) []byte { - var encoded bytes.Buffer - enc := base64.NewEncoder(base64.StdEncoding, &encoded) - _, err := enc.Write(input) - require.NoError(t, err) - require.NoError(t, enc.Close()) - return encoded.Bytes() -} - -func b64decode(t *testing.T, input []byte) []byte { - var decoded bytes.Buffer - dec := newBase64Decoder(&decoded) - _, err := dec.Write(input) - require.NoError(t, err) - require.NoError(t, dec.Close()) - return decoded.Bytes() -} - -func FuzzBase64Decoder(f *testing.F) { - f.Fuzz(func(t *testing.T, input []byte) { - encoded := b64encode(t, input) - decoded := b64decode(t, encoded) - t.Logf("input b64 = %q", encoded) - t.Logf("expected decoded = %#v", input) - t.Logf("actual decoded = %#v", decoded) - if !bytes.Equal(input, decoded) { - t.Fail() - } - }) -} diff --git a/lib/lowmemjson/borrowed_decode_test.go b/lib/lowmemjson/borrowed_decode_test.go deleted file mode 100644 index a1fd695..0000000 --- a/lib/lowmemjson/borrowed_decode_test.go +++ /dev/null @@ -1,2590 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package lowmemjson // MODIFIED - -import ( - "bytes" - "encoding" - "errors" - "fmt" - "image" - "io" // MODIFIED - "math" - "math/big" - "net" - "reflect" - "strconv" - "strings" - "testing" - "time" -) - -type T struct { - X string - Y int - Z int `json:"-"` -} - -type U struct { - Alphabet string `json:"alpha"` -} - -type V struct { - F1 any - F2 int32 - F3 Number - F4 *VOuter -} - -type VOuter struct { - V V -} - -type W struct { - S SS -} - -type P struct { - PP PP -} - -type PP struct { - T T - Ts []T -} - -type SS string - -func (*SS) UnmarshalJSON(data []byte) error { - return &UnmarshalTypeError{Value: "number", Type: reflect.TypeOf(SS(""))} -} - -// ifaceNumAsFloat64/ifaceNumAsNumber are used to test unmarshaling with and -// without UseNumber -var ifaceNumAsFloat64 = map[string]any{ - "k1": float64(1), - "k2": "s", - "k3": []any{float64(1), float64(2.0), float64(3e-3)}, - "k4": map[string]any{"kk1": "s", "kk2": float64(2)}, -} - -var ifaceNumAsNumber = map[string]any{ - "k1": Number("1"), - "k2": "s", - "k3": []any{Number("1"), Number("2.0"), Number("3e-3")}, - "k4": map[string]any{"kk1": "s", "kk2": Number("2")}, -} - -type tx struct { - x int -} - -type u8 uint8 - -// A type that can unmarshal itself. - -type unmarshaler struct { - T bool -} - -func (u *unmarshaler) UnmarshalJSON(b []byte) error { - *u = unmarshaler{true} // All we need to see that UnmarshalJSON is called. - return nil -} - -type ustruct struct { - M unmarshaler -} - -type unmarshalerText struct { - A, B string -} - -// needed for re-marshaling tests -func (u unmarshalerText) MarshalText() ([]byte, error) { - return []byte(u.A + ":" + u.B), nil -} - -func (u *unmarshalerText) UnmarshalText(b []byte) error { - pos := bytes.IndexByte(b, ':') - if pos == -1 { - return errors.New("missing separator") - } - u.A, u.B = string(b[:pos]), string(b[pos+1:]) - return nil -} - -var _ encoding.TextUnmarshaler = (*unmarshalerText)(nil) - -type ustructText struct { - M unmarshalerText -} - -// u8marshal is an integer type that can marshal/unmarshal itself. -type u8marshal uint8 - -func (u8 u8marshal) MarshalText() ([]byte, error) { - return []byte(fmt.Sprintf("u%d", u8)), nil -} - -var errMissingU8Prefix = errors.New("missing 'u' prefix") - -func (u8 *u8marshal) UnmarshalText(b []byte) error { - if !bytes.HasPrefix(b, []byte{'u'}) { - return errMissingU8Prefix - } - n, err := strconv.Atoi(string(b[1:])) - if err != nil { - return err - } - *u8 = u8marshal(n) - return nil -} - -var _ encoding.TextUnmarshaler = (*u8marshal)(nil) - -var ( - umtrue = unmarshaler{true} - umslice = []unmarshaler{{true}} - umstruct = ustruct{unmarshaler{true}} - - umtrueXY = unmarshalerText{"x", "y"} - umsliceXY = []unmarshalerText{{"x", "y"}} - umstructXY = ustructText{unmarshalerText{"x", "y"}} - - ummapXY = map[unmarshalerText]bool{{"x", "y"}: true} -) - -// Test data structures for anonymous fields. - -type Point struct { - Z int -} - -type Top struct { - Level0 int - Embed0 - *Embed0a - *Embed0b `json:"e,omitempty"` // treated as named - Embed0c `json:"-"` // ignored - Loop - Embed0p // has Point with X, Y, used - Embed0q // has Point with Z, used - embed // contains exported field -} - -type Embed0 struct { - Level1a int // overridden by Embed0a's Level1a with json tag - Level1b int // used because Embed0a's Level1b is renamed - Level1c int // used because Embed0a's Level1c is ignored - Level1d int // annihilated by Embed0a's Level1d - Level1e int `json:"x"` // annihilated by Embed0a.Level1e -} - -type Embed0a struct { - Level1a int `json:"Level1a,omitempty"` - Level1b int `json:"LEVEL1B,omitempty"` - Level1c int `json:"-"` - Level1d int // annihilated by Embed0's Level1d - Level1f int `json:"x"` // annihilated by Embed0's Level1e -} - -type Embed0b Embed0 - -type Embed0c Embed0 - -type Embed0p struct { - image.Point -} - -type Embed0q struct { - Point -} - -type embed struct { - Q int -} - -type Loop struct { - Loop1 int `json:",omitempty"` - Loop2 int `json:",omitempty"` - *Loop -} - -// From reflect test: -// The X in S6 and S7 annihilate, but they also block the X in S8.S9. -type S5 struct { - S6 - S7 - S8 -} - -type S6 struct { - X int -} - -type S7 S6 - -type S8 struct { - S9 -} - -type S9 struct { - X int - Y int -} - -// From reflect test: -// The X in S11.S6 and S12.S6 annihilate, but they also block the X in S13.S8.S9. -type S10 struct { - S11 - S12 - S13 -} - -type S11 struct { - S6 -} - -type S12 struct { - S6 -} - -type S13 struct { - S8 -} - -type Ambig struct { - // Given "hello", the first match should win. - First int `json:"HELLO"` - Second int `json:"Hello"` -} - -type XYZ struct { - X any - Y any - Z any -} - -type unexportedWithMethods struct{} - -func (unexportedWithMethods) F() {} - -type byteWithMarshalJSON byte - -func (b byteWithMarshalJSON) MarshalJSON() ([]byte, error) { - return []byte(fmt.Sprintf(`"Z%.2x"`, byte(b))), nil -} - -func (b *byteWithMarshalJSON) UnmarshalJSON(data []byte) error { - if len(data) != 5 || data[0] != '"' || data[1] != 'Z' || data[4] != '"' { - return fmt.Errorf("bad quoted string") - } - i, err := strconv.ParseInt(string(data[2:4]), 16, 8) - if err != nil { - return fmt.Errorf("bad hex") - } - *b = byteWithMarshalJSON(i) - return nil -} - -type byteWithPtrMarshalJSON byte - -func (b *byteWithPtrMarshalJSON) MarshalJSON() ([]byte, error) { - return byteWithMarshalJSON(*b).MarshalJSON() -} - -func (b *byteWithPtrMarshalJSON) UnmarshalJSON(data []byte) error { - return (*byteWithMarshalJSON)(b).UnmarshalJSON(data) -} - -type byteWithMarshalText byte - -func (b byteWithMarshalText) MarshalText() ([]byte, error) { - return []byte(fmt.Sprintf(`Z%.2x`, byte(b))), nil -} - -func (b *byteWithMarshalText) UnmarshalText(data []byte) error { - if len(data) != 3 || data[0] != 'Z' { - return fmt.Errorf("bad quoted string") - } - i, err := strconv.ParseInt(string(data[1:3]), 16, 8) - if err != nil { - return fmt.Errorf("bad hex") - } - *b = byteWithMarshalText(i) - return nil -} - -type byteWithPtrMarshalText byte - -func (b *byteWithPtrMarshalText) MarshalText() ([]byte, error) { - return byteWithMarshalText(*b).MarshalText() -} - -func (b *byteWithPtrMarshalText) UnmarshalText(data []byte) error { - return (*byteWithMarshalText)(b).UnmarshalText(data) -} - -type intWithMarshalJSON int - -func (b intWithMarshalJSON) MarshalJSON() ([]byte, error) { - return []byte(fmt.Sprintf(`"Z%.2x"`, int(b))), nil -} - -func (b *intWithMarshalJSON) UnmarshalJSON(data []byte) error { - if len(data) != 5 || data[0] != '"' || data[1] != 'Z' || data[4] != '"' { - return fmt.Errorf("bad quoted string") - } - i, err := strconv.ParseInt(string(data[2:4]), 16, 8) - if err != nil { - return fmt.Errorf("bad hex") - } - *b = intWithMarshalJSON(i) - return nil -} - -type intWithPtrMarshalJSON int - -func (b *intWithPtrMarshalJSON) MarshalJSON() ([]byte, error) { - return intWithMarshalJSON(*b).MarshalJSON() -} - -func (b *intWithPtrMarshalJSON) UnmarshalJSON(data []byte) error { - return (*intWithMarshalJSON)(b).UnmarshalJSON(data) -} - -type intWithMarshalText int - -func (b intWithMarshalText) MarshalText() ([]byte, error) { - return []byte(fmt.Sprintf(`Z%.2x`, int(b))), nil -} - -func (b *intWithMarshalText) UnmarshalText(data []byte) error { - if len(data) != 3 || data[0] != 'Z' { - return fmt.Errorf("bad quoted string") - } - i, err := strconv.ParseInt(string(data[1:3]), 16, 8) - if err != nil { - return fmt.Errorf("bad hex") - } - *b = intWithMarshalText(i) - return nil -} - -type intWithPtrMarshalText int - -func (b *intWithPtrMarshalText) MarshalText() ([]byte, error) { - return intWithMarshalText(*b).MarshalText() -} - -func (b *intWithPtrMarshalText) UnmarshalText(data []byte) error { - return (*intWithMarshalText)(b).UnmarshalText(data) -} - -type mapStringToStringData struct { - Data map[string]string `json:"data"` -} - -type unmarshalTest struct { - in string - ptr any // new(type) - out any - err error - useNumber bool - golden bool - disallowUnknownFields bool -} - -type B struct { - B bool `json:",string"` -} - -type DoublePtr struct { - I **int - J **int -} - -var unmarshalTests = []unmarshalTest{ - // basic types - {in: `true`, ptr: new(bool), out: true}, - {in: `1`, ptr: new(int), out: 1}, - {in: `1.2`, ptr: new(float64), out: 1.2}, - {in: `-5`, ptr: new(int16), out: int16(-5)}, - {in: `2`, ptr: new(Number), out: Number("2"), useNumber: true}, - {in: `2`, ptr: new(Number), out: Number("2")}, - {in: `2`, ptr: new(any), out: float64(2.0)}, - {in: `2`, ptr: new(any), out: Number("2"), useNumber: true}, - {in: `"a\u1234"`, ptr: new(string), out: "a\u1234"}, - {in: `"http:\/\/"`, ptr: new(string), out: "http://"}, - {in: `"g-clef: \uD834\uDD1E"`, ptr: new(string), out: "g-clef: \U0001D11E"}, - {in: `"invalid: \uD834x\uDD1E"`, ptr: new(string), out: "invalid: \uFFFDx\uFFFD"}, - {in: "null", ptr: new(any), out: nil}, - {in: `{"X": [1,2,3], "Y": 4}`, ptr: new(T), out: T{Y: 4}, err: &UnmarshalTypeError{"array", reflect.TypeOf(""), 7, "T", "X"}}, - {in: `{"X": 23}`, ptr: new(T), out: T{}, err: &UnmarshalTypeError{"number", reflect.TypeOf(""), 8, "T", "X"}}, {in: `{"x": 1}`, ptr: new(tx), out: tx{}}, - {in: `{"x": 1}`, ptr: new(tx), out: tx{}}, - {in: `{"x": 1}`, ptr: new(tx), err: fmt.Errorf("json: unknown field \"x\""), disallowUnknownFields: true}, - {in: `{"S": 23}`, ptr: new(W), out: W{}, err: &UnmarshalTypeError{"number", reflect.TypeOf(SS("")), 0, "W", "S"}}, - {in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: float64(1), F2: int32(2), F3: Number("3")}}, - {in: `{"F1":1,"F2":2,"F3":3}`, ptr: new(V), out: V{F1: Number("1"), F2: int32(2), F3: Number("3")}, useNumber: true}, - {in: `{"k1":1,"k2":"s","k3":[1,2.0,3e-3],"k4":{"kk1":"s","kk2":2}}`, ptr: new(any), out: ifaceNumAsFloat64}, - {in: `{"k1":1,"k2":"s","k3":[1,2.0,3e-3],"k4":{"kk1":"s","kk2":2}}`, ptr: new(any), out: ifaceNumAsNumber, useNumber: true}, - - // raw values with whitespace - {in: "\n true ", ptr: new(bool), out: true}, - {in: "\t 1 ", ptr: new(int), out: 1}, - {in: "\r 1.2 ", ptr: new(float64), out: 1.2}, - {in: "\t -5 \n", ptr: new(int16), out: int16(-5)}, - {in: "\t \"a\\u1234\" \n", ptr: new(string), out: "a\u1234"}, - - // Z has a "-" tag. - {in: `{"Y": 1, "Z": 2}`, ptr: new(T), out: T{Y: 1}}, - {in: `{"Y": 1, "Z": 2}`, ptr: new(T), err: fmt.Errorf("json: unknown field \"Z\""), disallowUnknownFields: true}, - - {in: `{"alpha": "abc", "alphabet": "xyz"}`, ptr: new(U), out: U{Alphabet: "abc"}}, - {in: `{"alpha": "abc", "alphabet": "xyz"}`, ptr: new(U), err: fmt.Errorf("json: unknown field \"alphabet\""), disallowUnknownFields: true}, - {in: `{"alpha": "abc"}`, ptr: new(U), out: U{Alphabet: "abc"}}, - {in: `{"alphabet": "xyz"}`, ptr: new(U), out: U{}}, - {in: `{"alphabet": "xyz"}`, ptr: new(U), err: fmt.Errorf("json: unknown field \"alphabet\""), disallowUnknownFields: true}, - - // syntax errors - {in: `{"X": "foo", "Y"}`, err: &SyntaxError{"invalid character '}' after object key", 17}}, - {in: `[1, 2, 3+]`, err: &SyntaxError{"invalid character '+' after array element", 9}}, - {in: `{"X":12x}`, err: &SyntaxError{"invalid character 'x' after object key:value pair", 8}, useNumber: true}, - {in: `[2, 3`, err: &SyntaxError{msg: "unexpected end of JSON input", Offset: 5}}, - {in: `{"F3": -}`, ptr: new(V), out: V{F3: Number("-")}, err: &SyntaxError{msg: "invalid character '}' in numeric literal", Offset: 9}}, - - // raw value errors - {in: "\x01 42", err: &SyntaxError{"invalid character '\\x01' looking for beginning of value", 1}}, - {in: " 42 \x01", err: &SyntaxError{"invalid character '\\x01' after top-level value", 5}}, - {in: "\x01 true", err: &SyntaxError{"invalid character '\\x01' looking for beginning of value", 1}}, - {in: " false \x01", err: &SyntaxError{"invalid character '\\x01' after top-level value", 8}}, - {in: "\x01 1.2", err: &SyntaxError{"invalid character '\\x01' looking for beginning of value", 1}}, - {in: " 3.4 \x01", err: &SyntaxError{"invalid character '\\x01' after top-level value", 6}}, - {in: "\x01 \"string\"", err: &SyntaxError{"invalid character '\\x01' looking for beginning of value", 1}}, - {in: " \"string\" \x01", err: &SyntaxError{"invalid character '\\x01' after top-level value", 11}}, - - // array tests - {in: `[1, 2, 3]`, ptr: new([3]int), out: [3]int{1, 2, 3}}, - {in: `[1, 2, 3]`, ptr: new([1]int), out: [1]int{1}}, - {in: `[1, 2, 3]`, ptr: new([5]int), out: [5]int{1, 2, 3, 0, 0}}, - {in: `[1, 2, 3]`, ptr: new(MustNotUnmarshalJSON), err: errors.New("MustNotUnmarshalJSON was used")}, - - // empty array to interface test - {in: `[]`, ptr: new([]any), out: []any{}}, - {in: `null`, ptr: new([]any), out: []any(nil)}, - {in: `{"T":[]}`, ptr: new(map[string]any), out: map[string]any{"T": []any{}}}, - {in: `{"T":null}`, ptr: new(map[string]any), out: map[string]any{"T": any(nil)}}, - - // composite tests - {in: allValueIndent, ptr: new(All), out: allValue}, - {in: allValueCompact, ptr: new(All), out: allValue}, - {in: allValueIndent, ptr: new(*All), out: &allValue}, - {in: allValueCompact, ptr: new(*All), out: &allValue}, - {in: pallValueIndent, ptr: new(All), out: pallValue}, - {in: pallValueCompact, ptr: new(All), out: pallValue}, - {in: pallValueIndent, ptr: new(*All), out: &pallValue}, - {in: pallValueCompact, ptr: new(*All), out: &pallValue}, - - // unmarshal interface test - {in: `{"T":false}`, ptr: new(unmarshaler), out: umtrue}, // use "false" so test will fail if custom unmarshaler is not called - {in: `{"T":false}`, ptr: new(*unmarshaler), out: &umtrue}, - {in: `[{"T":false}]`, ptr: new([]unmarshaler), out: umslice}, - {in: `[{"T":false}]`, ptr: new(*[]unmarshaler), out: &umslice}, - {in: `{"M":{"T":"x:y"}}`, ptr: new(ustruct), out: umstruct}, - - // UnmarshalText interface test - {in: `"x:y"`, ptr: new(unmarshalerText), out: umtrueXY}, - {in: `"x:y"`, ptr: new(*unmarshalerText), out: &umtrueXY}, - {in: `["x:y"]`, ptr: new([]unmarshalerText), out: umsliceXY}, - {in: `["x:y"]`, ptr: new(*[]unmarshalerText), out: &umsliceXY}, - {in: `{"M":"x:y"}`, ptr: new(ustructText), out: umstructXY}, - - // integer-keyed map test - { - in: `{"-1":"a","0":"b","1":"c"}`, - ptr: new(map[int]string), - out: map[int]string{-1: "a", 0: "b", 1: "c"}, - }, - { - in: `{"0":"a","10":"c","9":"b"}`, - ptr: new(map[u8]string), - out: map[u8]string{0: "a", 9: "b", 10: "c"}, - }, - { - in: `{"-9223372036854775808":"min","9223372036854775807":"max"}`, - ptr: new(map[int64]string), - out: map[int64]string{math.MinInt64: "min", math.MaxInt64: "max"}, - }, - { - in: `{"18446744073709551615":"max"}`, - ptr: new(map[uint64]string), - out: map[uint64]string{math.MaxUint64: "max"}, - }, - { - in: `{"0":false,"10":true}`, - ptr: new(map[uintptr]bool), - out: map[uintptr]bool{0: false, 10: true}, - }, - - // Check that MarshalText and UnmarshalText take precedence - // over default integer handling in map keys. - { - in: `{"u2":4}`, - ptr: new(map[u8marshal]int), - out: map[u8marshal]int{2: 4}, - }, - { - in: `{"2":4}`, - ptr: new(map[u8marshal]int), - err: errMissingU8Prefix, - }, - - // integer-keyed map errors - { - in: `{"abc":"abc"}`, - ptr: new(map[int]string), - err: &UnmarshalTypeError{Value: "number abc", Type: reflect.TypeOf(0), Offset: 2}, - }, - { - in: `{"256":"abc"}`, - ptr: new(map[uint8]string), - err: &UnmarshalTypeError{Value: "number 256", Type: reflect.TypeOf(uint8(0)), Offset: 2}, - }, - { - in: `{"128":"abc"}`, - ptr: new(map[int8]string), - err: &UnmarshalTypeError{Value: "number 128", Type: reflect.TypeOf(int8(0)), Offset: 2}, - }, - { - in: `{"-1":"abc"}`, - ptr: new(map[uint8]string), - err: &UnmarshalTypeError{Value: "number -1", Type: reflect.TypeOf(uint8(0)), Offset: 2}, - }, - { - in: `{"F":{"a":2,"3":4}}`, - ptr: new(map[string]map[int]int), - err: &UnmarshalTypeError{Value: "number a", Type: reflect.TypeOf(int(0)), Offset: 7}, - }, - { - in: `{"F":{"a":2,"3":4}}`, - ptr: new(map[string]map[uint]int), - err: &UnmarshalTypeError{Value: "number a", Type: reflect.TypeOf(uint(0)), Offset: 7}, - }, - - // Map keys can be encoding.TextUnmarshalers. - {in: `{"x:y":true}`, ptr: new(map[unmarshalerText]bool), out: ummapXY}, - // If multiple values for the same key exists, only the most recent value is used. - {in: `{"x:y":false,"x:y":true}`, ptr: new(map[unmarshalerText]bool), out: ummapXY}, - - { - in: `{ - "Level0": 1, - "Level1b": 2, - "Level1c": 3, - "x": 4, - "Level1a": 5, - "LEVEL1B": 6, - "e": { - "Level1a": 8, - "Level1b": 9, - "Level1c": 10, - "Level1d": 11, - "x": 12 - }, - "Loop1": 13, - "Loop2": 14, - "X": 15, - "Y": 16, - "Z": 17, - "Q": 18 - }`, - ptr: new(Top), - out: Top{ - Level0: 1, - Embed0: Embed0{ - Level1b: 2, - Level1c: 3, - }, - Embed0a: &Embed0a{ - Level1a: 5, - Level1b: 6, - }, - Embed0b: &Embed0b{ - Level1a: 8, - Level1b: 9, - Level1c: 10, - Level1d: 11, - Level1e: 12, - }, - Loop: Loop{ - Loop1: 13, - Loop2: 14, - }, - Embed0p: Embed0p{ - Point: image.Point{X: 15, Y: 16}, - }, - Embed0q: Embed0q{ - Point: Point{Z: 17}, - }, - embed: embed{ - Q: 18, - }, - }, - }, - { - in: `{"hello": 1}`, - ptr: new(Ambig), - out: Ambig{First: 1}, - }, - - { - in: `{"X": 1,"Y":2}`, - ptr: new(S5), - out: S5{S8: S8{S9: S9{Y: 2}}}, - }, - { - in: `{"X": 1,"Y":2}`, - ptr: new(S5), - err: fmt.Errorf("json: unknown field \"X\""), - disallowUnknownFields: true, - }, - { - in: `{"X": 1,"Y":2}`, - ptr: new(S10), - out: S10{S13: S13{S8: S8{S9: S9{Y: 2}}}}, - }, - { - in: `{"X": 1,"Y":2}`, - ptr: new(S10), - err: fmt.Errorf("json: unknown field \"X\""), - disallowUnknownFields: true, - }, - { - in: `{"I": 0, "I": null, "J": null}`, - ptr: new(DoublePtr), - out: DoublePtr{I: nil, J: nil}, - }, - - // invalid UTF-8 is coerced to valid UTF-8. - { - in: "\"hello\xffworld\"", - ptr: new(string), - out: "hello\ufffdworld", - }, - { - in: "\"hello\xc2\xc2world\"", - ptr: new(string), - out: "hello\ufffd\ufffdworld", - }, - { - in: "\"hello\xc2\xffworld\"", - ptr: new(string), - out: "hello\ufffd\ufffdworld", - }, - { - in: "\"hello\\ud800world\"", - ptr: new(string), - out: "hello\ufffdworld", - }, - { - in: "\"hello\\ud800\\ud800world\"", - ptr: new(string), - out: "hello\ufffd\ufffdworld", - }, - { - in: "\"hello\\ud800\\ud800world\"", - ptr: new(string), - out: "hello\ufffd\ufffdworld", - }, - { - in: "\"hello\xed\xa0\x80\xed\xb0\x80world\"", - ptr: new(string), - out: "hello\ufffd\ufffd\ufffd\ufffd\ufffd\ufffdworld", - }, - - // Used to be issue 8305, but time.Time implements encoding.TextUnmarshaler so this works now. - { - in: `{"2009-11-10T23:00:00Z": "hello world"}`, - ptr: new(map[time.Time]string), - out: map[time.Time]string{time.Date(2009, 11, 10, 23, 0, 0, 0, time.UTC): "hello world"}, - }, - - // issue 8305 - { - in: `{"2009-11-10T23:00:00Z": "hello world"}`, - ptr: new(map[Point]string), - err: &UnmarshalTypeError{Value: "object", Type: reflect.TypeOf(map[Point]string{}), Offset: 1}, - }, - { - in: `{"asdf": "hello world"}`, - ptr: new(map[unmarshaler]string), - err: &UnmarshalTypeError{Value: "object", Type: reflect.TypeOf(map[unmarshaler]string{}), Offset: 1}, - }, - - // related to issue 13783. - // Go 1.7 changed marshaling a slice of typed byte to use the methods on the byte type, - // similar to marshaling a slice of typed int. - // These tests check that, assuming the byte type also has valid decoding methods, - // either the old base64 string encoding or the new per-element encoding can be - // successfully unmarshaled. The custom unmarshalers were accessible in earlier - // versions of Go, even though the custom marshaler was not. - { - in: `"AQID"`, - ptr: new([]byteWithMarshalJSON), - out: []byteWithMarshalJSON{1, 2, 3}, - }, - { - in: `["Z01","Z02","Z03"]`, - ptr: new([]byteWithMarshalJSON), - out: []byteWithMarshalJSON{1, 2, 3}, - golden: true, - }, - { - in: `"AQID"`, - ptr: new([]byteWithMarshalText), - out: []byteWithMarshalText{1, 2, 3}, - }, - { - in: `["Z01","Z02","Z03"]`, - ptr: new([]byteWithMarshalText), - out: []byteWithMarshalText{1, 2, 3}, - golden: true, - }, - { - in: `"AQID"`, - ptr: new([]byteWithPtrMarshalJSON), - out: []byteWithPtrMarshalJSON{1, 2, 3}, - }, - { - in: `["Z01","Z02","Z03"]`, - ptr: new([]byteWithPtrMarshalJSON), - out: []byteWithPtrMarshalJSON{1, 2, 3}, - golden: true, - }, - { - in: `"AQID"`, - ptr: new([]byteWithPtrMarshalText), - out: []byteWithPtrMarshalText{1, 2, 3}, - }, - { - in: `["Z01","Z02","Z03"]`, - ptr: new([]byteWithPtrMarshalText), - out: []byteWithPtrMarshalText{1, 2, 3}, - golden: true, - }, - - // ints work with the marshaler but not the base64 []byte case - { - in: `["Z01","Z02","Z03"]`, - ptr: new([]intWithMarshalJSON), - out: []intWithMarshalJSON{1, 2, 3}, - golden: true, - }, - { - in: `["Z01","Z02","Z03"]`, - ptr: new([]intWithMarshalText), - out: []intWithMarshalText{1, 2, 3}, - golden: true, - }, - { - in: `["Z01","Z02","Z03"]`, - ptr: new([]intWithPtrMarshalJSON), - out: []intWithPtrMarshalJSON{1, 2, 3}, - golden: true, - }, - { - in: `["Z01","Z02","Z03"]`, - ptr: new([]intWithPtrMarshalText), - out: []intWithPtrMarshalText{1, 2, 3}, - golden: true, - }, - - {in: `0.000001`, ptr: new(float64), out: 0.000001, golden: true}, - {in: `1e-7`, ptr: new(float64), out: 1e-7, golden: true}, - {in: `100000000000000000000`, ptr: new(float64), out: 100000000000000000000.0, golden: true}, - {in: `1e+21`, ptr: new(float64), out: 1e21, golden: true}, - {in: `-0.000001`, ptr: new(float64), out: -0.000001, golden: true}, - {in: `-1e-7`, ptr: new(float64), out: -1e-7, golden: true}, - {in: `-100000000000000000000`, ptr: new(float64), out: -100000000000000000000.0, golden: true}, - {in: `-1e+21`, ptr: new(float64), out: -1e21, golden: true}, - {in: `999999999999999900000`, ptr: new(float64), out: 999999999999999900000.0, golden: true}, - {in: `9007199254740992`, ptr: new(float64), out: 9007199254740992.0, golden: true}, - {in: `9007199254740993`, ptr: new(float64), out: 9007199254740992.0, golden: false}, - - { - in: `{"V": {"F2": "hello"}}`, - ptr: new(VOuter), - err: &UnmarshalTypeError{ - Value: "string", - Struct: "V", - Field: "V.F2", - Type: reflect.TypeOf(int32(0)), - Offset: 20, - }, - }, - { - in: `{"V": {"F4": {}, "F2": "hello"}}`, - ptr: new(VOuter), - err: &UnmarshalTypeError{ - Value: "string", - Struct: "V", - Field: "V.F2", - Type: reflect.TypeOf(int32(0)), - Offset: 30, - }, - }, - - // issue 15146. - // invalid inputs in wrongStringTests below. - {in: `{"B":"true"}`, ptr: new(B), out: B{true}, golden: true}, - {in: `{"B":"false"}`, ptr: new(B), out: B{false}, golden: true}, - {in: `{"B": "maybe"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "maybe" into bool`)}, - {in: `{"B": "tru"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "tru" into bool`)}, - {in: `{"B": "False"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "False" into bool`)}, - {in: `{"B": "null"}`, ptr: new(B), out: B{false}}, - {in: `{"B": "nul"}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal "nul" into bool`)}, - {in: `{"B": [2, 3]}`, ptr: new(B), err: errors.New(`json: invalid use of ,string struct tag, trying to unmarshal unquoted value into bool`)}, - - // additional tests for disallowUnknownFields - { - in: `{ - "Level0": 1, - "Level1b": 2, - "Level1c": 3, - "x": 4, - "Level1a": 5, - "LEVEL1B": 6, - "e": { - "Level1a": 8, - "Level1b": 9, - "Level1c": 10, - "Level1d": 11, - "x": 12 - }, - "Loop1": 13, - "Loop2": 14, - "X": 15, - "Y": 16, - "Z": 17, - "Q": 18, - "extra": true - }`, - ptr: new(Top), - err: fmt.Errorf("json: unknown field \"extra\""), - disallowUnknownFields: true, - }, - { - in: `{ - "Level0": 1, - "Level1b": 2, - "Level1c": 3, - "x": 4, - "Level1a": 5, - "LEVEL1B": 6, - "e": { - "Level1a": 8, - "Level1b": 9, - "Level1c": 10, - "Level1d": 11, - "x": 12, - "extra": null - }, - "Loop1": 13, - "Loop2": 14, - "X": 15, - "Y": 16, - "Z": 17, - "Q": 18 - }`, - ptr: new(Top), - err: fmt.Errorf("json: unknown field \"extra\""), - disallowUnknownFields: true, - }, - // issue 26444 - // UnmarshalTypeError without field & struct values - { - in: `{"data":{"test1": "bob", "test2": 123}}`, - ptr: new(mapStringToStringData), - err: &UnmarshalTypeError{Value: "number", Type: reflect.TypeOf(""), Offset: 37, Struct: "mapStringToStringData", Field: "data"}, - }, - { - in: `{"data":{"test1": 123, "test2": "bob"}}`, - ptr: new(mapStringToStringData), - err: &UnmarshalTypeError{Value: "number", Type: reflect.TypeOf(""), Offset: 21, Struct: "mapStringToStringData", Field: "data"}, - }, - - // trying to decode JSON arrays or objects via TextUnmarshaler - { - in: `[1, 2, 3]`, - ptr: new(MustNotUnmarshalText), - err: &UnmarshalTypeError{Value: "array", Type: reflect.TypeOf(&MustNotUnmarshalText{}), Offset: 1}, - }, - { - in: `{"foo": "bar"}`, - ptr: new(MustNotUnmarshalText), - err: &UnmarshalTypeError{Value: "object", Type: reflect.TypeOf(&MustNotUnmarshalText{}), Offset: 1}, - }, - // #22369 - { - in: `{"PP": {"T": {"Y": "bad-type"}}}`, - ptr: new(P), - err: &UnmarshalTypeError{ - Value: "string", - Struct: "T", - Field: "PP.T.Y", - Type: reflect.TypeOf(int(0)), - Offset: 29, - }, - }, - { - in: `{"Ts": [{"Y": 1}, {"Y": 2}, {"Y": "bad-type"}]}`, - ptr: new(PP), - err: &UnmarshalTypeError{ - Value: "string", - Struct: "T", - Field: "Ts.Y", - Type: reflect.TypeOf(int(0)), - Offset: 29, - }, - }, - // #14702 - { - in: `invalid`, - ptr: new(Number), - err: &SyntaxError{ - msg: "invalid character 'i' looking for beginning of value", - Offset: 1, - }, - }, - { - in: `"invalid"`, - ptr: new(Number), - err: fmt.Errorf("json: invalid number literal, trying to unmarshal %q into Number", `"invalid"`), - }, - { - in: `{"A":"invalid"}`, - ptr: new(struct{ A Number }), - err: fmt.Errorf("json: invalid number literal, trying to unmarshal %q into Number", `"invalid"`), - }, - { - in: `{"A":"invalid"}`, - ptr: new(struct { - A Number `json:",string"` - }), - err: fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into json.Number", `invalid`), - }, - { - in: `{"A":"invalid"}`, - ptr: new(map[string]Number), - err: fmt.Errorf("json: invalid number literal, trying to unmarshal %q into Number", `"invalid"`), - }, -} - -func TestMarshal(t *testing.T) { - b, err := Marshal(allValue) - if err != nil { - t.Fatalf("Marshal allValue: %v", err) - } - if string(b) != allValueCompact { - t.Errorf("Marshal allValueCompact") - diff(t, b, []byte(allValueCompact)) - return - } - - b, err = Marshal(pallValue) - if err != nil { - t.Fatalf("Marshal pallValue: %v", err) - } - if string(b) != pallValueCompact { - t.Errorf("Marshal pallValueCompact") - diff(t, b, []byte(pallValueCompact)) - return - } -} - -var badUTF8 = []struct { - in, out string -}{ - {"hello\xffworld", `"hello\ufffdworld"`}, - {"", `""`}, - {"\xff", `"\ufffd"`}, - {"\xff\xff", `"\ufffd\ufffd"`}, - {"a\xffb", `"a\ufffdb"`}, - {"\xe6\x97\xa5\xe6\x9c\xac\xff\xaa\x9e", `"日本\ufffd\ufffd\ufffd"`}, -} - -func TestMarshalBadUTF8(t *testing.T) { - for _, tt := range badUTF8 { - b, err := Marshal(tt.in) - if string(b) != tt.out || err != nil { - t.Errorf("Marshal(%q) = %#q, %v, want %#q, nil", tt.in, b, err, tt.out) - } - } -} - -func TestMarshalNumberZeroVal(t *testing.T) { - var n Number - out, err := Marshal(n) - if err != nil { - t.Fatal(err) - } - outStr := string(out) - if outStr != "0" { - t.Fatalf("Invalid zero val for Number: %q", outStr) - } -} - -func TestMarshalEmbeds(t *testing.T) { - t.Skip() // TODO - top := &Top{ - Level0: 1, - Embed0: Embed0{ - Level1b: 2, - Level1c: 3, - }, - Embed0a: &Embed0a{ - Level1a: 5, - Level1b: 6, - }, - Embed0b: &Embed0b{ - Level1a: 8, - Level1b: 9, - Level1c: 10, - Level1d: 11, - Level1e: 12, - }, - Loop: Loop{ - Loop1: 13, - Loop2: 14, - }, - Embed0p: Embed0p{ - Point: image.Point{X: 15, Y: 16}, - }, - Embed0q: Embed0q{ - Point: Point{Z: 17}, - }, - embed: embed{ - Q: 18, - }, - } - b, err := Marshal(top) - if err != nil { - t.Fatal(err) - } - want := "{\"Level0\":1,\"Level1b\":2,\"Level1c\":3,\"Level1a\":5,\"LEVEL1B\":6,\"e\":{\"Level1a\":8,\"Level1b\":9,\"Level1c\":10,\"Level1d\":11,\"x\":12},\"Loop1\":13,\"Loop2\":14,\"X\":15,\"Y\":16,\"Z\":17,\"Q\":18}" - if string(b) != want { - t.Errorf("Wrong marshal result.\n got: %q\nwant: %q", b, want) - } -} - -func equalError(a, b error) bool { - if a == nil { - return b == nil - } - if b == nil { - return a == nil - } - return true // a.Error() == b.Error() // MODIFIED -} - -func TestUnmarshal(t *testing.T) { - t.Skip() // TODO - for i, tt := range unmarshalTests { - scan := &ReEncoder{ - Out: io.Discard, - } - in := []byte(tt.in) - if _, err := scan.Write(in); err != nil { - if !equalError(err, tt.err) { - t.Errorf("#%d: checkValid: %#v\n\n%s", i, err, tt.in) - continue - } - } - if tt.ptr == nil { - continue - } - - typ := reflect.TypeOf(tt.ptr) - if typ.Kind() != reflect.Pointer { - t.Errorf("#%d: unmarshalTest.ptr %T is not a pointer type", i, tt.ptr) - continue - } - typ = typ.Elem() - - // v = new(right-type) - v := reflect.New(typ) - - if !reflect.DeepEqual(tt.ptr, v.Interface()) { - // There's no reason for ptr to point to non-zero data, - // as we decode into new(right-type), so the data is - // discarded. - // This can easily mean tests that silently don't test - // what they should. To test decoding into existing - // data, see TestPrefilled. - t.Errorf("#%d: unmarshalTest.ptr %#v is not a pointer to a zero value", i, tt.ptr) - continue - } - - dec := NewDecoder(bytes.NewReader(in)) - if tt.useNumber { - dec.UseNumber() - } - if tt.disallowUnknownFields { - dec.DisallowUnknownFields() - } - if err := dec.Decode(v.Interface()); !equalError(err, tt.err) { - t.Errorf("#%d: %v, want %v", i, err, tt.err) - continue - } else if err != nil { - continue - } - if !reflect.DeepEqual(v.Elem().Interface(), tt.out) { - t.Errorf("#%d: mismatch\nhave: %#+v\nwant: %#+v\n\n%s", i, v.Elem().Interface(), tt.out, tt.in) - data, _ := Marshal(v.Elem().Interface()) - println(string(data)) - data, _ = Marshal(tt.out) - println(string(data)) - continue - } - - // Check round trip also decodes correctly. - if tt.err == nil { - enc, err := Marshal(v.Interface()) - if err != nil { - t.Errorf("#%d: error re-marshaling: %v", i, err) - continue - } - if tt.golden && !bytes.Equal(enc, in) { - t.Errorf("#%d: remarshal mismatch:\nhave: %s\nwant: %s", i, enc, in) - } - vv := reflect.New(reflect.TypeOf(tt.ptr).Elem()) - dec = NewDecoder(bytes.NewReader(enc)) - if tt.useNumber { - dec.UseNumber() - } - if err := dec.Decode(vv.Interface()); err != nil { - t.Errorf("#%d: error re-unmarshaling %#q: %v", i, enc, err) - continue - } - if !reflect.DeepEqual(v.Elem().Interface(), vv.Elem().Interface()) { - t.Errorf("#%d: mismatch\nhave: %#+v\nwant: %#+v", i, v.Elem().Interface(), vv.Elem().Interface()) - t.Errorf(" In: %q", strings.Map(noSpace, string(in))) - t.Errorf("Marshal: %q", strings.Map(noSpace, string(enc))) - continue - } - } - } -} - -func TestUnmarshalMarshal(t *testing.T) { - initBig() - var v any - if err := Unmarshal(jsonBig, &v); err != nil { - t.Fatalf("Unmarshal: %v", err) - } - b, err := Marshal(v) - if err != nil { - t.Fatalf("Marshal: %v", err) - } - if !bytes.Equal(jsonBig, b) { - t.Errorf("Marshal jsonBig") - diff(t, b, jsonBig) - return - } -} - -var numberTests = []struct { - in string - i int64 - intErr string - f float64 - floatErr string -}{ - {in: "-1.23e1", intErr: "strconv.ParseInt: parsing \"-1.23e1\": invalid syntax", f: -1.23e1}, - {in: "-12", i: -12, f: -12.0}, - {in: "1e1000", intErr: "strconv.ParseInt: parsing \"1e1000\": invalid syntax", floatErr: "strconv.ParseFloat: parsing \"1e1000\": value out of range"}, -} - -// Independent of Decode, basic coverage of the accessors in Number -func TestNumberAccessors(t *testing.T) { - for _, tt := range numberTests { - n := Number(tt.in) - if s := n.String(); s != tt.in { - t.Errorf("Number(%q).String() is %q", tt.in, s) - } - if i, err := n.Int64(); err == nil && tt.intErr == "" && i != tt.i { - t.Errorf("Number(%q).Int64() is %d", tt.in, i) - } else if (err == nil && tt.intErr != "") || (err != nil && err.Error() != tt.intErr) { - t.Errorf("Number(%q).Int64() wanted error %q but got: %v", tt.in, tt.intErr, err) - } - if f, err := n.Float64(); err == nil && tt.floatErr == "" && f != tt.f { - t.Errorf("Number(%q).Float64() is %g", tt.in, f) - } else if (err == nil && tt.floatErr != "") || (err != nil && err.Error() != tt.floatErr) { - t.Errorf("Number(%q).Float64() wanted error %q but got: %v", tt.in, tt.floatErr, err) - } - } -} - -func TestLargeByteSlice(t *testing.T) { - s0 := make([]byte, 2000) - for i := range s0 { - s0[i] = byte(i) - } - b, err := Marshal(s0) - if err != nil { - t.Fatalf("Marshal: %v", err) - } - var s1 []byte - if err := Unmarshal(b, &s1); err != nil { - t.Fatalf("Unmarshal: %v", err) - } - if !bytes.Equal(s0, s1) { - t.Errorf("Marshal large byte slice") - diff(t, s0, s1) - } -} - -type Xint struct { - X int -} - -func TestUnmarshalInterface(t *testing.T) { - var xint Xint - var i any = &xint - if err := Unmarshal([]byte(`{"X":1}`), &i); err != nil { - t.Fatalf("Unmarshal: %v", err) - } - if xint.X != 1 { - t.Fatalf("Did not write to xint") - } -} - -func TestUnmarshalPtrPtr(t *testing.T) { - var xint Xint - pxint := &xint - if err := Unmarshal([]byte(`{"X":1}`), &pxint); err != nil { - t.Fatalf("Unmarshal: %v", err) - } - if xint.X != 1 { - t.Fatalf("Did not write to xint") - } -} - -func TestEscape(t *testing.T) { - const input = `"foobar"` + " [\u2028 \u2029]" - const expected = `"\"foobar\"\u003chtml\u003e [\u2028 \u2029]"` - b, err := Marshal(input) - if err != nil { - t.Fatalf("Marshal error: %v", err) - } - if s := string(b); s != expected { - t.Errorf("Encoding of [%s]:\n got [%s]\nwant [%s]", input, s, expected) - } -} - -// WrongString is a struct that's misusing the ,string modifier. -type WrongString struct { - Message string `json:"result,string"` -} - -type wrongStringTest struct { - in, err string -} - -var wrongStringTests = []wrongStringTest{ - {`{"result":"x"}`, `json: invalid use of ,string struct tag, trying to unmarshal "x" into string`}, - {`{"result":"foo"}`, `json: invalid use of ,string struct tag, trying to unmarshal "foo" into string`}, - {`{"result":"123"}`, `json: invalid use of ,string struct tag, trying to unmarshal "123" into string`}, - {`{"result":123}`, `json: invalid use of ,string struct tag, trying to unmarshal unquoted value into string`}, - {`{"result":"\""}`, `json: invalid use of ,string struct tag, trying to unmarshal "\"" into string`}, - {`{"result":"\"foo"}`, `json: invalid use of ,string struct tag, trying to unmarshal "\"foo" into string`}, -} - -// If people misuse the ,string modifier, the error message should be -// helpful, telling the user that they're doing it wrong. -func TestErrorMessageFromMisusedString(t *testing.T) { - for n, tt := range wrongStringTests { - r := strings.NewReader(tt.in) - var s WrongString - err := NewDecoder(r).Decode(&s) - got := fmt.Sprintf("%v", err) - if err == nil { // if got != tt.err { // MODIFIED - t.Errorf("%d. got err = %q, want %q", n, got, tt.err) - } - } -} - -func noSpace(c rune) rune { - if isSpace(byte(c)) { //only used for ascii - return -1 - } - return c -} - -type All struct { - Bool bool - Int int - Int8 int8 - Int16 int16 - Int32 int32 - Int64 int64 - Uint uint - Uint8 uint8 - Uint16 uint16 - Uint32 uint32 - Uint64 uint64 - Uintptr uintptr - Float32 float32 - Float64 float64 - - Foo string `json:"bar"` - Foo2 string `json:"bar2,dummyopt"` - - IntStr int64 `json:",string"` - UintptrStr uintptr `json:",string"` - - PBool *bool - PInt *int - PInt8 *int8 - PInt16 *int16 - PInt32 *int32 - PInt64 *int64 - PUint *uint - PUint8 *uint8 - PUint16 *uint16 - PUint32 *uint32 - PUint64 *uint64 - PUintptr *uintptr - PFloat32 *float32 - PFloat64 *float64 - - String string - PString *string - - Map map[string]Small - MapP map[string]*Small - PMap *map[string]Small - PMapP *map[string]*Small - - EmptyMap map[string]Small - NilMap map[string]Small - - Slice []Small - SliceP []*Small - PSlice *[]Small - PSliceP *[]*Small - - EmptySlice []Small - NilSlice []Small - - StringSlice []string - ByteSlice []byte - - Small Small - PSmall *Small - PPSmall **Small - - Interface any - PInterface *any - - unexported int -} - -type Small struct { - Tag string -} - -var allValue = All{ - Bool: true, - Int: 2, - Int8: 3, - Int16: 4, - Int32: 5, - Int64: 6, - Uint: 7, - Uint8: 8, - Uint16: 9, - Uint32: 10, - Uint64: 11, - Uintptr: 12, - Float32: 14.1, - Float64: 15.1, - Foo: "foo", - Foo2: "foo2", - IntStr: 42, - UintptrStr: 44, - String: "16", - Map: map[string]Small{ - "17": {Tag: "tag17"}, - "18": {Tag: "tag18"}, - }, - MapP: map[string]*Small{ - "19": {Tag: "tag19"}, - "20": nil, - }, - EmptyMap: map[string]Small{}, - Slice: []Small{{Tag: "tag20"}, {Tag: "tag21"}}, - SliceP: []*Small{{Tag: "tag22"}, nil, {Tag: "tag23"}}, - EmptySlice: []Small{}, - StringSlice: []string{"str24", "str25", "str26"}, - ByteSlice: []byte{27, 28, 29}, - Small: Small{Tag: "tag30"}, - PSmall: &Small{Tag: "tag31"}, - Interface: 5.2, -} - -var pallValue = All{ - PBool: &allValue.Bool, - PInt: &allValue.Int, - PInt8: &allValue.Int8, - PInt16: &allValue.Int16, - PInt32: &allValue.Int32, - PInt64: &allValue.Int64, - PUint: &allValue.Uint, - PUint8: &allValue.Uint8, - PUint16: &allValue.Uint16, - PUint32: &allValue.Uint32, - PUint64: &allValue.Uint64, - PUintptr: &allValue.Uintptr, - PFloat32: &allValue.Float32, - PFloat64: &allValue.Float64, - PString: &allValue.String, - PMap: &allValue.Map, - PMapP: &allValue.MapP, - PSlice: &allValue.Slice, - PSliceP: &allValue.SliceP, - PPSmall: &allValue.PSmall, - PInterface: &allValue.Interface, -} - -var allValueIndent = `{ - "Bool": true, - "Int": 2, - "Int8": 3, - "Int16": 4, - "Int32": 5, - "Int64": 6, - "Uint": 7, - "Uint8": 8, - "Uint16": 9, - "Uint32": 10, - "Uint64": 11, - "Uintptr": 12, - "Float32": 14.1, - "Float64": 15.1, - "bar": "foo", - "bar2": "foo2", - "IntStr": "42", - "UintptrStr": "44", - "PBool": null, - "PInt": null, - "PInt8": null, - "PInt16": null, - "PInt32": null, - "PInt64": null, - "PUint": null, - "PUint8": null, - "PUint16": null, - "PUint32": null, - "PUint64": null, - "PUintptr": null, - "PFloat32": null, - "PFloat64": null, - "String": "16", - "PString": null, - "Map": { - "17": { - "Tag": "tag17" - }, - "18": { - "Tag": "tag18" - } - }, - "MapP": { - "19": { - "Tag": "tag19" - }, - "20": null - }, - "PMap": null, - "PMapP": null, - "EmptyMap": {}, - "NilMap": null, - "Slice": [ - { - "Tag": "tag20" - }, - { - "Tag": "tag21" - } - ], - "SliceP": [ - { - "Tag": "tag22" - }, - null, - { - "Tag": "tag23" - } - ], - "PSlice": null, - "PSliceP": null, - "EmptySlice": [], - "NilSlice": null, - "StringSlice": [ - "str24", - "str25", - "str26" - ], - "ByteSlice": "Gxwd", - "Small": { - "Tag": "tag30" - }, - "PSmall": { - "Tag": "tag31" - }, - "PPSmall": null, - "Interface": 5.2, - "PInterface": null -}` - -var allValueCompact = strings.Map(noSpace, allValueIndent) - -var pallValueIndent = `{ - "Bool": false, - "Int": 0, - "Int8": 0, - "Int16": 0, - "Int32": 0, - "Int64": 0, - "Uint": 0, - "Uint8": 0, - "Uint16": 0, - "Uint32": 0, - "Uint64": 0, - "Uintptr": 0, - "Float32": 0, - "Float64": 0, - "bar": "", - "bar2": "", - "IntStr": "0", - "UintptrStr": "0", - "PBool": true, - "PInt": 2, - "PInt8": 3, - "PInt16": 4, - "PInt32": 5, - "PInt64": 6, - "PUint": 7, - "PUint8": 8, - "PUint16": 9, - "PUint32": 10, - "PUint64": 11, - "PUintptr": 12, - "PFloat32": 14.1, - "PFloat64": 15.1, - "String": "", - "PString": "16", - "Map": null, - "MapP": null, - "PMap": { - "17": { - "Tag": "tag17" - }, - "18": { - "Tag": "tag18" - } - }, - "PMapP": { - "19": { - "Tag": "tag19" - }, - "20": null - }, - "EmptyMap": null, - "NilMap": null, - "Slice": null, - "SliceP": null, - "PSlice": [ - { - "Tag": "tag20" - }, - { - "Tag": "tag21" - } - ], - "PSliceP": [ - { - "Tag": "tag22" - }, - null, - { - "Tag": "tag23" - } - ], - "EmptySlice": null, - "NilSlice": null, - "StringSlice": null, - "ByteSlice": null, - "Small": { - "Tag": "" - }, - "PSmall": null, - "PPSmall": { - "Tag": "tag31" - }, - "Interface": null, - "PInterface": 5.2 -}` - -var pallValueCompact = strings.Map(noSpace, pallValueIndent) - -func TestRefUnmarshal(t *testing.T) { - type S struct { - // Ref is defined in encode_test.go. - R0 Ref - R1 *Ref - R2 RefText - R3 *RefText - } - want := S{ - R0: 12, - R1: new(Ref), - R2: 13, - R3: new(RefText), - } - *want.R1 = 12 - *want.R3 = 13 - - var got S - if err := Unmarshal([]byte(`{"R0":"ref","R1":"ref","R2":"ref","R3":"ref"}`), &got); err != nil { - t.Fatalf("Unmarshal: %v", err) - } - if !reflect.DeepEqual(got, want) { - t.Errorf("got %+v, want %+v", got, want) - } -} - -// Test that the empty string doesn't panic decoding when ,string is specified -// Issue 3450 -func TestEmptyString(t *testing.T) { - type T2 struct { - Number1 int `json:",string"` - Number2 int `json:",string"` - } - data := `{"Number1":"1", "Number2":""}` - dec := NewDecoder(strings.NewReader(data)) - var t2 T2 - err := dec.Decode(&t2) - if err == nil { - t.Fatal("Decode: did not return error") - } - if t2.Number1 != 1 { - t.Fatal("Decode: did not set Number1") - } -} - -// Test that a null for ,string is not replaced with the previous quoted string (issue 7046). -// It should also not be an error (issue 2540, issue 8587). -func TestNullString(t *testing.T) { - type T struct { - A int `json:",string"` - B int `json:",string"` - C *int `json:",string"` - } - data := []byte(`{"A": "1", "B": null, "C": null}`) - var s T - s.B = 1 - s.C = new(int) - *s.C = 2 - err := Unmarshal(data, &s) - if err != nil { - t.Fatalf("Unmarshal: %v", err) - } - if s.B != 1 || s.C != nil { - t.Fatalf("after Unmarshal, s.B=%d, s.C=%p, want 1, nil", s.B, s.C) - } -} - -func intp(x int) *int { - p := new(int) - *p = x - return p -} - -func intpp(x *int) **int { - pp := new(*int) - *pp = x - return pp -} - -var interfaceSetTests = []struct { - pre any - json string - post any -}{ - {"foo", `"bar"`, "bar"}, - {"foo", `2`, 2.0}, - {"foo", `true`, true}, - {"foo", `null`, nil}, - - {nil, `null`, nil}, - {new(int), `null`, nil}, - {(*int)(nil), `null`, nil}, - {new(*int), `null`, new(*int)}, - {(**int)(nil), `null`, nil}, - {intp(1), `null`, nil}, - {intpp(nil), `null`, intpp(nil)}, - {intpp(intp(1)), `null`, intpp(nil)}, -} - -func TestInterfaceSet(t *testing.T) { - t.Skip() // TODO - for _, tt := range interfaceSetTests { - b := struct{ X any }{tt.pre} - blob := `{"X":` + tt.json + `}` - if err := Unmarshal([]byte(blob), &b); err != nil { - t.Errorf("Unmarshal %#q: %v", blob, err) - continue - } - if !reflect.DeepEqual(b.X, tt.post) { - t.Errorf("Unmarshal %#q into %#v: X=%#v, want %#v", blob, tt.pre, b.X, tt.post) - } - } -} - -type NullTest struct { - Bool bool - Int int - Int8 int8 - Int16 int16 - Int32 int32 - Int64 int64 - Uint uint - Uint8 uint8 - Uint16 uint16 - Uint32 uint32 - Uint64 uint64 - Float32 float32 - Float64 float64 - String string - PBool *bool - Map map[string]string - Slice []string - Interface any - - PRaw *RawMessage - PTime *time.Time - PBigInt *big.Int - PText *MustNotUnmarshalText - PBuffer *bytes.Buffer // has methods, just not relevant ones - PStruct *struct{} - - Raw RawMessage - Time time.Time - BigInt big.Int - Text MustNotUnmarshalText - Buffer bytes.Buffer - Struct struct{} -} - -// JSON null values should be ignored for primitives and string values instead of resulting in an error. -// Issue 2540 -func TestUnmarshalNulls(t *testing.T) { - // Unmarshal docs: - // The JSON null value unmarshals into an interface, map, pointer, or slice - // by setting that Go value to nil. Because null is often used in JSON to mean - // ``not present,'' unmarshaling a JSON null into any other Go type has no effect - // on the value and produces no error. - - jsonData := []byte(`{ - "Bool" : null, - "Int" : null, - "Int8" : null, - "Int16" : null, - "Int32" : null, - "Int64" : null, - "Uint" : null, - "Uint8" : null, - "Uint16" : null, - "Uint32" : null, - "Uint64" : null, - "Float32" : null, - "Float64" : null, - "String" : null, - "PBool": null, - "Map": null, - "Slice": null, - "Interface": null, - "PRaw": null, - "PTime": null, - "PBigInt": null, - "PText": null, - "PBuffer": null, - "PStruct": null, - "Raw": null, - "Time": null, - "BigInt": null, - "Text": null, - "Buffer": null, - "Struct": null - }`) - nulls := NullTest{ - Bool: true, - Int: 2, - Int8: 3, - Int16: 4, - Int32: 5, - Int64: 6, - Uint: 7, - Uint8: 8, - Uint16: 9, - Uint32: 10, - Uint64: 11, - Float32: 12.1, - Float64: 13.1, - String: "14", - PBool: new(bool), - Map: map[string]string{}, - Slice: []string{}, - Interface: new(MustNotUnmarshalJSON), - PRaw: new(RawMessage), - PTime: new(time.Time), - PBigInt: new(big.Int), - PText: new(MustNotUnmarshalText), - PStruct: new(struct{}), - PBuffer: new(bytes.Buffer), - Raw: RawMessage("123"), - Time: time.Unix(123456789, 0), - BigInt: *big.NewInt(123), - } - - before := nulls.Time.String() - - err := Unmarshal(jsonData, &nulls) - if err != nil { - t.Errorf("Unmarshal of null values failed: %v", err) - } - if !nulls.Bool || nulls.Int != 2 || nulls.Int8 != 3 || nulls.Int16 != 4 || nulls.Int32 != 5 || nulls.Int64 != 6 || - nulls.Uint != 7 || nulls.Uint8 != 8 || nulls.Uint16 != 9 || nulls.Uint32 != 10 || nulls.Uint64 != 11 || - nulls.Float32 != 12.1 || nulls.Float64 != 13.1 || nulls.String != "14" { - t.Errorf("Unmarshal of null values affected primitives") - } - - if nulls.PBool != nil { - t.Errorf("Unmarshal of null did not clear nulls.PBool") - } - if nulls.Map != nil { - t.Errorf("Unmarshal of null did not clear nulls.Map") - } - if nulls.Slice != nil { - t.Errorf("Unmarshal of null did not clear nulls.Slice") - } - if nulls.Interface != nil { - t.Errorf("Unmarshal of null did not clear nulls.Interface") - } - if nulls.PRaw != nil { - t.Errorf("Unmarshal of null did not clear nulls.PRaw") - } - if nulls.PTime != nil { - t.Errorf("Unmarshal of null did not clear nulls.PTime") - } - if nulls.PBigInt != nil { - t.Errorf("Unmarshal of null did not clear nulls.PBigInt") - } - if nulls.PText != nil { - t.Errorf("Unmarshal of null did not clear nulls.PText") - } - if nulls.PBuffer != nil { - t.Errorf("Unmarshal of null did not clear nulls.PBuffer") - } - if nulls.PStruct != nil { - t.Errorf("Unmarshal of null did not clear nulls.PStruct") - } - - if string(nulls.Raw) != "null" { - t.Errorf("Unmarshal of RawMessage null did not record null: %v", string(nulls.Raw)) - } - if nulls.Time.String() != before { - t.Errorf("Unmarshal of time.Time null set time to %v", nulls.Time.String()) - } - if nulls.BigInt.String() != "123" { - t.Errorf("Unmarshal of big.Int null set int to %v", nulls.BigInt.String()) - } -} - -type MustNotUnmarshalJSON struct{} - -func (x MustNotUnmarshalJSON) UnmarshalJSON(data []byte) error { - return errors.New("MustNotUnmarshalJSON was used") -} - -type MustNotUnmarshalText struct{} - -func (x MustNotUnmarshalText) UnmarshalText(text []byte) error { - return errors.New("MustNotUnmarshalText was used") -} - -func TestStringKind(t *testing.T) { - type stringKind string - - var m1, m2 map[stringKind]int - m1 = map[stringKind]int{ - "foo": 42, - } - - data, err := Marshal(m1) - if err != nil { - t.Errorf("Unexpected error marshaling: %v", err) - } - - err = Unmarshal(data, &m2) - if err != nil { - t.Errorf("Unexpected error unmarshaling: %v", err) - } - - if !reflect.DeepEqual(m1, m2) { - t.Error("Items should be equal after encoding and then decoding") - } -} - -// Custom types with []byte as underlying type could not be marshaled -// and then unmarshaled. -// Issue 8962. -func TestByteKind(t *testing.T) { - type byteKind []byte - - a := byteKind("hello") - - data, err := Marshal(a) - if err != nil { - t.Error(err) - } - if !reflect.DeepEqual(data, []byte(`"aGVsbG8="`)) { // MODIFIED - t.Errorf("expected %q == %q", data, `"aGVsbG8="`) // MODIFIED - } // MODIFIED - var b byteKind - err = Unmarshal(data, &b) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(a, b) { - t.Errorf("expected %v == %v", a, b) - } -} - -// The fix for issue 8962 introduced a regression. -// Issue 12921. -func TestSliceOfCustomByte(t *testing.T) { - type Uint8 uint8 - - a := []Uint8("hello") - - data, err := Marshal(a) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(data, []byte(`"aGVsbG8="`)) { // MODIFIED - t.Errorf("expected %q == %q", data, `"aGVsbG8="`) // MODIFIED - } // MODIFIED - var b []Uint8 - err = Unmarshal(data, &b) - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(a, b) { - t.Fatalf("expected %v == %v", a, b) - } -} - -var decodeTypeErrorTests = []struct { - dest any - src string -}{ - {new(string), `{"user": "name"}`}, // issue 4628. - {new(error), `{}`}, // issue 4222 - {new(error), `[]`}, - {new(error), `""`}, - {new(error), `123`}, - {new(error), `true`}, -} - -func TestUnmarshalTypeError(t *testing.T) { - for _, item := range decodeTypeErrorTests { - err := Unmarshal([]byte(item.src), item.dest) - if err == nil { // if _, ok := err.(*UnmarshalTypeError); !ok { // MODIFIED - t.Errorf("expected type error for Unmarshal(%q, type %T): got %T", - item.src, item.dest, err) - } - } -} - -var unmarshalSyntaxTests = []string{ - "tru", - "fals", - "nul", - "123e", - `"hello`, - `[1,2,3`, - `{"key":1`, - `{"key":1,`, -} - -func TestUnmarshalSyntax(t *testing.T) { - var x any - for _, src := range unmarshalSyntaxTests { - err := Unmarshal([]byte(src), &x) - if err == nil { // _, ok := err.(*SyntaxError); !ok { // MODIFIED - t.Errorf("expected syntax error for Unmarshal(%q): got %T", src, err) - } - } -} - -// Test handling of unexported fields that should be ignored. -// Issue 4660 -type unexportedFields struct { - Name string - m map[string]any `json:"-"` - m2 map[string]any `json:"abcd"` - - s []int `json:"-"` -} - -func TestUnmarshalUnexported(t *testing.T) { - input := `{"Name": "Bob", "m": {"x": 123}, "m2": {"y": 456}, "abcd": {"z": 789}, "s": [2, 3]}` - want := &unexportedFields{Name: "Bob"} - - out := &unexportedFields{} - err := Unmarshal([]byte(input), out) - if err != nil { - t.Errorf("got error %v, expected nil", err) - } - if !reflect.DeepEqual(out, want) { - t.Errorf("got %q, want %q", out, want) - } -} - -// Time3339 is a time.Time which encodes to and from JSON -// as an RFC 3339 time in UTC. -type Time3339 time.Time - -func (t *Time3339) UnmarshalJSON(b []byte) error { - if len(b) < 2 || b[0] != '"' || b[len(b)-1] != '"' { - return fmt.Errorf("types: failed to unmarshal non-string value %q as an RFC 3339 time", b) - } - tm, err := time.Parse(time.RFC3339, string(b[1:len(b)-1])) - if err != nil { - return err - } - *t = Time3339(tm) - return nil -} - -func TestUnmarshalJSONLiteralError(t *testing.T) { - var t3 Time3339 - err := Unmarshal([]byte(`"0000-00-00T00:00:00Z"`), &t3) - if err == nil { - t.Fatalf("expected error; got time %v", time.Time(t3)) - } - if !strings.Contains(err.Error(), "range") { - t.Errorf("got err = %v; want out of range error", err) - } -} - -// Test that extra object elements in an array do not result in a -// "data changing underfoot" error. -// Issue 3717 -func TestSkipArrayObjects(t *testing.T) { - json := `[{}]` - var dest [0]any - - err := Unmarshal([]byte(json), &dest) - if err != nil { - t.Errorf("got error %q, want nil", err) - } -} - -// Test semantics of pre-filled data, such as struct fields, map elements, -// slices, and arrays. -// Issues 4900 and 8837, among others. -func TestPrefilled(t *testing.T) { - // Values here change, cannot reuse table across runs. - var prefillTests = []struct { - in string - ptr any - out any - }{ - { - in: `{"X": 1, "Y": 2}`, - ptr: &XYZ{X: float32(3), Y: int16(4), Z: 1.5}, - out: &XYZ{X: float64(1), Y: float64(2), Z: 1.5}, - }, - { - in: `{"X": 1, "Y": 2}`, - ptr: &map[string]any{"X": float32(3), "Y": int16(4), "Z": 1.5}, - out: &map[string]any{"X": float64(1), "Y": float64(2), "Z": 1.5}, - }, - { - in: `[2]`, - ptr: &[]int{1}, - out: &[]int{2}, - }, - { - in: `[2, 3]`, - ptr: &[]int{1}, - out: &[]int{2, 3}, - }, - { - in: `[2, 3]`, - ptr: &[...]int{1}, - out: &[...]int{2}, - }, - { - in: `[3]`, - ptr: &[...]int{1, 2}, - out: &[...]int{3, 0}, - }, - } - - for _, tt := range prefillTests { - ptrstr := fmt.Sprintf("%v", tt.ptr) - err := Unmarshal([]byte(tt.in), tt.ptr) // tt.ptr edited here - if err != nil { - t.Errorf("Unmarshal: %v", err) - } - if !reflect.DeepEqual(tt.ptr, tt.out) { - t.Errorf("Unmarshal(%#q, %s): have %v, want %v", tt.in, ptrstr, tt.ptr, tt.out) - } - } -} - -var invalidUnmarshalTests = []struct { - v any - want string -}{ - {nil, "json: Unmarshal(nil)"}, - {struct{}{}, "json: Unmarshal(non-pointer struct {})"}, - {(*int)(nil), "json: Unmarshal(nil *int)"}, -} - -func TestInvalidUnmarshal(t *testing.T) { - buf := []byte(`{"a":"1"}`) - for _, tt := range invalidUnmarshalTests { - err := Unmarshal(buf, tt.v) - if err == nil { - t.Errorf("Unmarshal expecting error, got nil") - continue - } - if got := err.Error(); got != tt.want { - t.Errorf("Unmarshal = %q; want %q", got, tt.want) - } - } -} - -var invalidUnmarshalTextTests = []struct { - v any - want string -}{ - {nil, "json: Unmarshal(nil)"}, - {struct{}{}, "json: Unmarshal(non-pointer struct {})"}, - {(*int)(nil), "json: Unmarshal(nil *int)"}, - {new(net.IP), "json: cannot unmarshal number into Go value of type *net.IP"}, -} - -func TestInvalidUnmarshalText(t *testing.T) { - buf := []byte(`123`) - for _, tt := range invalidUnmarshalTextTests { - err := Unmarshal(buf, tt.v) - if err == nil { - t.Errorf("Unmarshal expecting error, got nil") - continue - } - // if got := err.Error(); got != tt.want { // MODIFIED - // t.Errorf("Unmarshal = %q; want %q", got, tt.want) // MODIFIED - // } // MODIFIED - } -} - -// Test that string option is ignored for invalid types. -// Issue 9812. -func TestInvalidStringOption(t *testing.T) { - num := 0 - item := struct { - T time.Time `json:",string"` - M map[string]string `json:",string"` - S []string `json:",string"` - A [1]string `json:",string"` - I any `json:",string"` - P *int `json:",string"` - }{M: make(map[string]string), S: make([]string, 0), I: num, P: &num} - - data, err := Marshal(item) - if err != nil { - t.Fatalf("Marshal: %v", err) - } - - err = Unmarshal(data, &item) - if err != nil { - t.Fatalf("Unmarshal: %v", err) - } -} - -// Test unmarshal behavior with regards to embedded unexported structs. -// -// (Issue 21357) If the embedded struct is a pointer and is unallocated, -// this returns an error because unmarshal cannot set the field. -// -// (Issue 24152) If the embedded struct is given an explicit name, -// ensure that the normal unmarshal logic does not panic in reflect. -// -// (Issue 28145) If the embedded struct is given an explicit name and has -// exported methods, don't cause a panic trying to get its value. -func TestUnmarshalEmbeddedUnexported(t *testing.T) { - t.Skip() // TODO - type ( - embed1 struct{ Q int } - embed2 struct{ Q int } - embed3 struct { - Q int64 `json:",string"` - } - S1 struct { - *embed1 - R int - } - S2 struct { - *embed1 - Q int - } - S3 struct { - embed1 - R int - } - S4 struct { - *embed1 - embed2 - } - S5 struct { - *embed3 - R int - } - S6 struct { - embed1 `json:"embed1"` - } - S7 struct { - embed1 `json:"embed1"` - embed2 - } - S8 struct { - embed1 `json:"embed1"` - embed2 `json:"embed2"` - Q int - } - S9 struct { - unexportedWithMethods `json:"embed"` - } - ) - - tests := []struct { - in string - ptr any - out any - err error - }{{ - // Error since we cannot set S1.embed1, but still able to set S1.R. - in: `{"R":2,"Q":1}`, - ptr: new(S1), - out: &S1{R: 2}, - err: fmt.Errorf("json: cannot set embedded pointer to unexported struct: json.embed1"), - }, { - // The top level Q field takes precedence. - in: `{"Q":1}`, - ptr: new(S2), - out: &S2{Q: 1}, - }, { - // No issue with non-pointer variant. - in: `{"R":2,"Q":1}`, - ptr: new(S3), - out: &S3{embed1: embed1{Q: 1}, R: 2}, - }, { - // No error since both embedded structs have field R, which annihilate each other. - // Thus, no attempt is made at setting S4.embed1. - in: `{"R":2}`, - ptr: new(S4), - out: new(S4), - }, { - // Error since we cannot set S5.embed1, but still able to set S5.R. - in: `{"R":2,"Q":1}`, - ptr: new(S5), - out: &S5{R: 2}, - err: fmt.Errorf("json: cannot set embedded pointer to unexported struct: json.embed3"), - }, { - // Issue 24152, ensure decodeState.indirect does not panic. - in: `{"embed1": {"Q": 1}}`, - ptr: new(S6), - out: &S6{embed1{1}}, - }, { - // Issue 24153, check that we can still set forwarded fields even in - // the presence of a name conflict. - // - // This relies on obscure behavior of reflect where it is possible - // to set a forwarded exported field on an unexported embedded struct - // even though there is a name conflict, even when it would have been - // impossible to do so according to Go visibility rules. - // Go forbids this because it is ambiguous whether S7.Q refers to - // S7.embed1.Q or S7.embed2.Q. Since embed1 and embed2 are unexported, - // it should be impossible for an external package to set either Q. - // - // It is probably okay for a future reflect change to break this. - in: `{"embed1": {"Q": 1}, "Q": 2}`, - ptr: new(S7), - out: &S7{embed1{1}, embed2{2}}, - }, { - // Issue 24153, similar to the S7 case. - in: `{"embed1": {"Q": 1}, "embed2": {"Q": 2}, "Q": 3}`, - ptr: new(S8), - out: &S8{embed1{1}, embed2{2}, 3}, - }, { - // Issue 228145, similar to the cases above. - in: `{"embed": {}}`, - ptr: new(S9), - out: &S9{}, - }} - - for i, tt := range tests { - err := Unmarshal([]byte(tt.in), tt.ptr) - if !equalError(err, tt.err) { - t.Errorf("#%d: %v, want %v", i, err, tt.err) - } - if !reflect.DeepEqual(tt.ptr, tt.out) { - t.Errorf("#%d: mismatch\ngot: %#+v\nwant: %#+v", i, tt.ptr, tt.out) - } - } -} - -func TestUnmarshalErrorAfterMultipleJSON(t *testing.T) { - t.Skip() // TODO - tests := []struct { - in string - err error - }{{ - in: `1 false null :`, - err: &SyntaxError{"invalid character ':' looking for beginning of value", 14}, - }, { - in: `1 [] [,]`, - err: &SyntaxError{"invalid character ',' looking for beginning of value", 7}, - }, { - in: `1 [] [true:]`, - err: &SyntaxError{"invalid character ':' after array element", 11}, - }, { - in: `1 {} {"x"=}`, - err: &SyntaxError{"invalid character '=' after object key", 14}, - }, { - in: `falsetruenul#`, - err: &SyntaxError{"invalid character '#' in literal null (expecting 'l')", 13}, - }} - for i, tt := range tests { - dec := NewDecoder(strings.NewReader(tt.in)) - var err error - for { - var v any - if err = dec.Decode(&v); err != nil { - break - } - } - if !reflect.DeepEqual(err, tt.err) { - t.Errorf("#%d: got %#v, want %#v", i, err, tt.err) - } - } -} - -type unmarshalPanic struct{} - -func (unmarshalPanic) UnmarshalJSON([]byte) error { panic(0xdead) } - -func TestUnmarshalPanic(t *testing.T) { - defer func() { - if got := recover(); !reflect.DeepEqual(got, 0xdead) { - t.Errorf("panic() = (%T)(%v), want 0xdead", got, got) - } - }() - Unmarshal([]byte("{}"), &unmarshalPanic{}) - t.Fatalf("Unmarshal should have panicked") -} - -// The decoder used to hang if decoding into an interface pointing to its own address. -// See golang.org/issues/31740. -func TestUnmarshalRecursivePointer(t *testing.T) { - t.Skip() // TODO - var v any - v = &v - data := []byte(`{"a": "b"}`) - - if err := Unmarshal(data, v); err != nil { - t.Fatal(err) - } -} - -type textUnmarshalerString string - -func (m *textUnmarshalerString) UnmarshalText(text []byte) error { - *m = textUnmarshalerString(strings.ToLower(string(text))) - return nil -} - -// Test unmarshal to a map, where the map key is a user defined type. -// See golang.org/issues/34437. -func TestUnmarshalMapWithTextUnmarshalerStringKey(t *testing.T) { - var p map[textUnmarshalerString]string - if err := Unmarshal([]byte(`{"FOO": "1"}`), &p); err != nil { - t.Fatalf("Unmarshal unexpected error: %v", err) - } - - if _, ok := p["foo"]; !ok { - t.Errorf(`Key "foo" does not exist in map: %v`, p) - } -} - -func TestUnmarshalRescanLiteralMangledUnquote(t *testing.T) { - // See golang.org/issues/38105. - var p map[textUnmarshalerString]string - if err := Unmarshal([]byte(`{"开源":"12345开源"}`), &p); err != nil { - t.Fatalf("Unmarshal unexpected error: %v", err) - } - if _, ok := p["开源"]; !ok { - t.Errorf(`Key "开源" does not exist in map: %v`, p) - } - - // See golang.org/issues/38126. - type T struct { - F1 string `json:"F1,string"` - } - t1 := T{"aaa\tbbb"} - - b, err := Marshal(t1) - if err != nil { - t.Fatalf("Marshal unexpected error: %v", err) - } - var t2 T - if err := Unmarshal(b, &t2); err != nil { - t.Fatalf("Unmarshal unexpected error: %v", err) - } - if t1 != t2 { - t.Errorf("Marshal and Unmarshal roundtrip mismatch: want %q got %q", t1, t2) - } - - // See golang.org/issues/39555. - input := map[textUnmarshalerString]string{"FOO": "", `"`: ""} - - encoded, err := Marshal(input) - if err != nil { - t.Fatalf("Marshal unexpected error: %v", err) - } - var got map[textUnmarshalerString]string - if err := Unmarshal(encoded, &got); err != nil { - t.Fatalf("Unmarshal unexpected error: %v", err) - } - want := map[textUnmarshalerString]string{"foo": "", `"`: ""} - if !reflect.DeepEqual(want, got) { - t.Fatalf("Unexpected roundtrip result:\nwant: %q\ngot: %q", want, got) - } -} - -func TestUnmarshalMaxDepth(t *testing.T) { - t.Skip() // TODO - testcases := []struct { - name string - data string - errMaxDepth bool - }{ - { - name: "ArrayUnderMaxNestingDepth", - data: `{"a":` + strings.Repeat(`[`, 10000-1) + strings.Repeat(`]`, 10000-1) + `}`, - errMaxDepth: false, - }, - { - name: "ArrayOverMaxNestingDepth", - data: `{"a":` + strings.Repeat(`[`, 10000) + strings.Repeat(`]`, 10000) + `}`, - errMaxDepth: true, - }, - { - name: "ArrayOverStackDepth", - data: `{"a":` + strings.Repeat(`[`, 3000000) + strings.Repeat(`]`, 3000000) + `}`, - errMaxDepth: true, - }, - { - name: "ObjectUnderMaxNestingDepth", - data: `{"a":` + strings.Repeat(`{"a":`, 10000-1) + `0` + strings.Repeat(`}`, 10000-1) + `}`, - errMaxDepth: false, - }, - { - name: "ObjectOverMaxNestingDepth", - data: `{"a":` + strings.Repeat(`{"a":`, 10000) + `0` + strings.Repeat(`}`, 10000) + `}`, - errMaxDepth: true, - }, - { - name: "ObjectOverStackDepth", - data: `{"a":` + strings.Repeat(`{"a":`, 3000000) + `0` + strings.Repeat(`}`, 3000000) + `}`, - errMaxDepth: true, - }, - } - - targets := []struct { - name string - newValue func() any - }{ - { - name: "unstructured", - newValue: func() any { - var v any - return &v - }, - }, - { - name: "typed named field", - newValue: func() any { - v := struct { - A any `json:"a"` - }{} - return &v - }, - }, - { - name: "typed missing field", - newValue: func() any { - v := struct { - B any `json:"b"` - }{} - return &v - }, - }, - { - name: "custom unmarshaler", - newValue: func() any { - v := unmarshaler{} - return &v - }, - }, - } - - for _, tc := range testcases { - for _, target := range targets { - t.Run(target.name+"-"+tc.name, func(t *testing.T) { - err := Unmarshal([]byte(tc.data), target.newValue()) - if !tc.errMaxDepth { - if err != nil { - t.Errorf("unexpected error: %v", err) - } - } else { - if err == nil { - t.Errorf("expected error containing 'exceeded max depth', got none") - } else if !strings.Contains(err.Error(), "exceeded max depth") { - t.Errorf("expected error containing 'exceeded max depth', got: %v", err) - } - } - }) - } - } -} diff --git a/lib/lowmemjson/borrowed_encode_test.go b/lib/lowmemjson/borrowed_encode_test.go deleted file mode 100644 index 9659910..0000000 --- a/lib/lowmemjson/borrowed_encode_test.go +++ /dev/null @@ -1,1208 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package lowmemjson // MODIFIED - -import ( - "bytes" - "encoding" - "fmt" - "log" - "math" - "reflect" - "regexp" - "strconv" - "testing" - "unicode" -) - -type Optionals struct { - Sr string `json:"sr"` - So string `json:"so,omitempty"` - Sw string `json:"-"` - - Ir int `json:"omitempty"` // actually named omitempty, not an option - Io int `json:"io,omitempty"` - - Slr []string `json:"slr,random"` //nolint:staticcheck // testing handling of unknown options // MODIFIED - Slo []string `json:"slo,omitempty"` - - Mr map[string]any `json:"mr"` - Mo map[string]any `json:",omitempty"` - - Fr float64 `json:"fr"` - Fo float64 `json:"fo,omitempty"` - - Br bool `json:"br"` - Bo bool `json:"bo,omitempty"` - - Ur uint `json:"ur"` - Uo uint `json:"uo,omitempty"` - - Str struct{} `json:"str"` - Sto struct{} `json:"sto,omitempty"` -} - -var optionalsExpected = `{ - "sr": "", - "omitempty": 0, - "slr": null, - "mr": {}, - "fr": 0, - "br": false, - "ur": 0, - "str": {}, - "sto": {} -}` - -func TestOmitEmpty(t *testing.T) { - var o Optionals - o.Sw = "something" - o.Mr = map[string]any{} - o.Mo = map[string]any{} - - got, err := MarshalIndent(&o, "", " ") - if err != nil { - t.Fatal(err) - } - if got := string(got); got != optionalsExpected { - t.Errorf(" got: %s\nwant: %s\n", got, optionalsExpected) - } -} - -type StringTag struct { - BoolStr bool `json:",string"` - IntStr int64 `json:",string"` - UintptrStr uintptr `json:",string"` - StrStr string `json:",string"` - NumberStr Number `json:",string"` -} - -func TestRoundtripStringTag(t *testing.T) { - tests := []struct { - name string - in StringTag - want string // empty to just test that we roundtrip - }{ - { - name: "AllTypes", - in: StringTag{ - BoolStr: true, - IntStr: 42, - UintptrStr: 44, - StrStr: "xzbit", - NumberStr: "46", - }, - want: `{ - "BoolStr": "true", - "IntStr": "42", - "UintptrStr": "44", - "StrStr": "\"xzbit\"", - "NumberStr": "46" - }`, - }, - { - // See golang.org/issues/38173. - name: "StringDoubleEscapes", - in: StringTag{ - StrStr: "\b\f\n\r\t\"\\", - NumberStr: "0", // just to satisfy the roundtrip - }, - want: `{ - "BoolStr": "false", - "IntStr": "0", - "UintptrStr": "0", - "StrStr": "\"\\u0008\\u000c\\n\\r\\t\\\"\\\\\"", - "NumberStr": "0" - }`, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - // Indent with a tab prefix to make the multi-line string - // literals in the table nicer to read. - got, err := MarshalIndent(&test.in, "\t\t\t", "\t") - if err != nil { - t.Fatal(err) - } - if got := string(got); got != test.want { - t.Fatalf(" got: %s\nwant: %s\n", got, test.want) - } - - // Verify that it round-trips. - var s2 StringTag - if err := Unmarshal(got, &s2); err != nil { - t.Fatalf("Decode: %v", err) - } - if !reflect.DeepEqual(test.in, s2) { - t.Fatalf("decode didn't match.\nsource: %#v\nEncoded as:\n%s\ndecode: %#v", test.in, string(got), s2) - } - }) - } -} - -// byte slices are special even if they're renamed types. -type renamedByte byte -type renamedByteSlice []byte -type renamedRenamedByteSlice []renamedByte - -func TestEncodeRenamedByteSlice(t *testing.T) { - s := renamedByteSlice("abc") - result, err := Marshal(s) - if err != nil { - t.Fatal(err) - } - expect := `"YWJj"` - if string(result) != expect { - t.Errorf(" got %s want %s", result, expect) - } - r := renamedRenamedByteSlice("abc") - result, err = Marshal(r) - if err != nil { - t.Fatal(err) - } - if string(result) != expect { - t.Errorf(" got %s want %s", result, expect) - } -} - -type SamePointerNoCycle struct { - Ptr1, Ptr2 *SamePointerNoCycle -} - -var samePointerNoCycle = &SamePointerNoCycle{} - -type PointerCycle struct { - Ptr *PointerCycle -} - -var pointerCycle = &PointerCycle{} - -type PointerCycleIndirect struct { - Ptrs []any -} - -type RecursiveSlice []RecursiveSlice - -var ( - pointerCycleIndirect = &PointerCycleIndirect{} - mapCycle = make(map[string]any) - sliceCycle = []any{nil} - sliceNoCycle = []any{nil, nil} - recursiveSliceCycle = []RecursiveSlice{nil} -) - -func init() { - ptr := &SamePointerNoCycle{} - samePointerNoCycle.Ptr1 = ptr - samePointerNoCycle.Ptr2 = ptr - - pointerCycle.Ptr = pointerCycle - pointerCycleIndirect.Ptrs = []any{pointerCycleIndirect} - - mapCycle["x"] = mapCycle - sliceCycle[0] = sliceCycle - sliceNoCycle[1] = sliceNoCycle[:1] - for i := startDetectingCyclesAfter; i > 0; i-- { - sliceNoCycle = []any{sliceNoCycle} - } - recursiveSliceCycle[0] = recursiveSliceCycle -} - -func TestSamePointerNoCycle(t *testing.T) { - if _, err := Marshal(samePointerNoCycle); err != nil { - t.Fatalf("unexpected error: %v", err) - } -} - -func TestSliceNoCycle(t *testing.T) { - if _, err := Marshal(sliceNoCycle); err != nil { - t.Fatalf("unexpected error: %v", err) - } -} - -var unsupportedValues = []any{ - math.NaN(), - math.Inf(-1), - math.Inf(1), - //pointerCycle, // MODIFIED - //pointerCycleIndirect, // MODIFIED - //mapCycle, // MODIFIED - //sliceCycle, // MODIFIED - //recursiveSliceCycle, // MODIFIED -} - -func TestUnsupportedValues(t *testing.T) { - for _, v := range unsupportedValues { - if _, err := Marshal(v); err != nil { - if _, ok := err.(*UnsupportedValueError); !ok { - t.Errorf("for %v, got %T want UnsupportedValueError", v, err) - } - } else { - t.Errorf("for %v, expected error", v) - } - } -} - -// Issue 43207 -func TestMarshalTextFloatMap(t *testing.T) { - m := map[textfloat]string{ - textfloat(math.NaN()): "1", - textfloat(math.NaN()): "1", - } - got, err := Marshal(m) - if err != nil { - t.Errorf("Marshal() error: %v", err) - } - want := `{"TF:NaN":"1","TF:NaN":"1"}` - if string(got) != want { - t.Errorf("Marshal() = %s, want %s", got, want) - } -} - -// Ref has Marshaler and Unmarshaler methods with pointer receiver. -type Ref int - -func (*Ref) MarshalJSON() ([]byte, error) { - return []byte(`"ref"`), nil -} - -func (r *Ref) UnmarshalJSON([]byte) error { - *r = 12 - return nil -} - -// Val has Marshaler methods with value receiver. -type Val int - -func (Val) MarshalJSON() ([]byte, error) { - return []byte(`"val"`), nil -} - -// RefText has Marshaler and Unmarshaler methods with pointer receiver. -type RefText int - -func (*RefText) MarshalText() ([]byte, error) { - return []byte(`"ref"`), nil -} - -func (r *RefText) UnmarshalText([]byte) error { - *r = 13 - return nil -} - -// ValText has Marshaler methods with value receiver. -type ValText int - -func (ValText) MarshalText() ([]byte, error) { - return []byte(`"val"`), nil -} - -func TestRefValMarshal(t *testing.T) { - var s = struct { - R0 Ref - R1 *Ref - R2 RefText - R3 *RefText - V0 Val - V1 *Val - V2 ValText - V3 *ValText - }{ - R0: 12, - R1: new(Ref), - R2: 14, - R3: new(RefText), - V0: 13, - V1: new(Val), - V2: 15, - V3: new(ValText), - } - const want = `{"R0":"ref","R1":"ref","R2":"\"ref\"","R3":"\"ref\"","V0":"val","V1":"val","V2":"\"val\"","V3":"\"val\""}` - b, err := Marshal(&s) - if err != nil { - t.Fatalf("Marshal: %v", err) - } - if got := string(b); got != want { - t.Errorf("got %q, want %q", got, want) - } -} - -// C implements Marshaler and returns unescaped JSON. -type C int - -func (C) MarshalJSON() ([]byte, error) { - return []byte(`"<&>"`), nil -} - -// CText implements Marshaler and returns unescaped text. -type CText int - -func (CText) MarshalText() ([]byte, error) { - return []byte(`"<&>"`), nil -} - -func TestMarshalerEscaping(t *testing.T) { - t.Skip() // MODIFIED - var c C - want := `"\u003c\u0026\u003e"` - b, err := Marshal(c) - if err != nil { - t.Fatalf("Marshal(c): %v", err) - } - if got := string(b); got != want { - t.Errorf("Marshal(c) = %#q, want %#q", got, want) - } - - var ct CText - want = `"\"\u003c\u0026\u003e\""` - b, err = Marshal(ct) - if err != nil { - t.Fatalf("Marshal(ct): %v", err) - } - if got := string(b); got != want { - t.Errorf("Marshal(ct) = %#q, want %#q", got, want) - } -} - -func TestAnonymousFields(t *testing.T) { - tests := []struct { - label string // Test name - makeInput func() any // Function to create input value - want string // Expected JSON output - }{{ - // Both S1 and S2 have a field named X. From the perspective of S, - // it is ambiguous which one X refers to. - // This should not serialize either field. - label: "AmbiguousField", - makeInput: func() any { - type ( - S1 struct{ x, X int } - S2 struct{ x, X int } - S struct { - S1 - S2 - } - ) - return S{S1{1, 2}, S2{3, 4}} - }, - want: `{}`, - }, { - label: "DominantField", - // Both S1 and S2 have a field named X, but since S has an X field as - // well, it takes precedence over S1.X and S2.X. - makeInput: func() any { - type ( - S1 struct{ x, X int } - S2 struct{ x, X int } - S struct { - S1 - S2 - x, X int - } - ) - return S{S1{1, 2}, S2{3, 4}, 5, 6} - }, - want: `{"X":6}`, - }, { - // Unexported embedded field of non-struct type should not be serialized. - label: "UnexportedEmbeddedInt", - makeInput: func() any { - type ( - myInt int - S struct{ myInt } - ) - return S{5} - }, - want: `{}`, - }, { - // Exported embedded field of non-struct type should be serialized. - label: "ExportedEmbeddedInt", - makeInput: func() any { - type ( - MyInt int - S struct{ MyInt } - ) - return S{5} - }, - want: `{"MyInt":5}`, - }, { - // Unexported embedded field of pointer to non-struct type - // should not be serialized. - label: "UnexportedEmbeddedIntPointer", - makeInput: func() any { - type ( - myInt int - S struct{ *myInt } - ) - s := S{new(myInt)} - *s.myInt = 5 - return s - }, - want: `{}`, - }, { - // Exported embedded field of pointer to non-struct type - // should be serialized. - label: "ExportedEmbeddedIntPointer", - makeInput: func() any { - type ( - MyInt int - S struct{ *MyInt } - ) - s := S{new(MyInt)} - *s.MyInt = 5 - return s - }, - want: `{"MyInt":5}`, - }, { - // Exported fields of embedded structs should have their - // exported fields be serialized regardless of whether the struct types - // themselves are exported. - label: "EmbeddedStruct", - makeInput: func() any { - type ( - s1 struct{ x, X int } - S2 struct{ y, Y int } - S struct { - s1 - S2 - } - ) - return S{s1{1, 2}, S2{3, 4}} - }, - want: `{"X":2,"Y":4}`, - }, { - // Exported fields of pointers to embedded structs should have their - // exported fields be serialized regardless of whether the struct types - // themselves are exported. - label: "EmbeddedStructPointer", - makeInput: func() any { - type ( - s1 struct{ x, X int } - S2 struct{ y, Y int } - S struct { - *s1 - *S2 - } - ) - return S{&s1{1, 2}, &S2{3, 4}} - }, - want: `{"X":2,"Y":4}`, - }, { - // Exported fields on embedded unexported structs at multiple levels - // of nesting should still be serialized. - label: "NestedStructAndInts", - makeInput: func() any { - type ( - MyInt1 int - MyInt2 int - myInt int - s2 struct { - MyInt2 - myInt - } - s1 struct { - MyInt1 - myInt - s2 - } - S struct { - s1 - myInt - } - ) - return S{s1{1, 2, s2{3, 4}}, 6} - }, - want: `{"MyInt1":1,"MyInt2":3}`, - }, { - // If an anonymous struct pointer field is nil, we should ignore - // the embedded fields behind it. Not properly doing so may - // result in the wrong output or reflect panics. - label: "EmbeddedFieldBehindNilPointer", - makeInput: func() any { - type ( - S2 struct{ Field string } - S struct{ *S2 } - ) - return S{} - }, - want: `{}`, - }} - - for _, tt := range tests { - t.Run(tt.label, func(t *testing.T) { - b, err := Marshal(tt.makeInput()) - if err != nil { - t.Fatalf("Marshal() = %v, want nil error", err) - } - if string(b) != tt.want { - t.Fatalf("Marshal() = %q, want %q", b, tt.want) - } - }) - } -} - -type BugA struct { - S string -} - -type BugB struct { - BugA - S string -} - -type BugC struct { - S string -} - -// Legal Go: We never use the repeated embedded field (S). -type BugX struct { - A int - BugA - BugB -} - -// golang.org/issue/16042. -// Even if a nil interface value is passed in, as long as -// it implements Marshaler, it should be marshaled. -type nilJSONMarshaler string - -func (nm *nilJSONMarshaler) MarshalJSON() ([]byte, error) { - if nm == nil { - return Marshal("0zenil0") - } - return Marshal("zenil:" + string(*nm)) -} - -// golang.org/issue/34235. -// Even if a nil interface value is passed in, as long as -// it implements encoding.TextMarshaler, it should be marshaled. -type nilTextMarshaler string - -func (nm *nilTextMarshaler) MarshalText() ([]byte, error) { - if nm == nil { - return []byte("0zenil0"), nil - } - return []byte("zenil:" + string(*nm)), nil -} - -// See golang.org/issue/16042 and golang.org/issue/34235. -func TestNilMarshal(t *testing.T) { - testCases := []struct { - v any - want string - }{ - {v: nil, want: `null`}, - {v: new(float64), want: `0`}, - {v: []any(nil), want: `null`}, - {v: []string(nil), want: `null`}, - {v: map[string]string(nil), want: `null`}, - {v: []byte(nil), want: `null`}, - {v: struct{ M string }{"gopher"}, want: `{"M":"gopher"}`}, - {v: struct{ M Marshaler }{}, want: `{"M":null}`}, - {v: struct{ M Marshaler }{(*nilJSONMarshaler)(nil)}, want: `{"M":"0zenil0"}`}, - {v: struct{ M any }{(*nilJSONMarshaler)(nil)}, want: `{"M":null}`}, - {v: struct{ M encoding.TextMarshaler }{}, want: `{"M":null}`}, - {v: struct{ M encoding.TextMarshaler }{(*nilTextMarshaler)(nil)}, want: `{"M":"0zenil0"}`}, - {v: struct{ M any }{(*nilTextMarshaler)(nil)}, want: `{"M":null}`}, - } - - for _, tt := range testCases { - out, err := Marshal(tt.v) - if err != nil || string(out) != tt.want { - t.Errorf("Marshal(%#v) = %#q, %#v, want %#q, nil", tt.v, out, err, tt.want) - continue - } - } -} - -// Issue 5245. -func TestEmbeddedBug(t *testing.T) { - v := BugB{ - BugA{"A"}, - "B", - } - b, err := Marshal(v) - if err != nil { - t.Fatal("Marshal:", err) - } - want := `{"S":"B"}` - got := string(b) - if got != want { - t.Fatalf("Marshal: got %s want %s", got, want) - } - // Now check that the duplicate field, S, does not appear. - x := BugX{ - A: 23, - } - b, err = Marshal(x) - if err != nil { - t.Fatal("Marshal:", err) - } - want = `{"A":23}` - got = string(b) - if got != want { - t.Fatalf("Marshal: got %s want %s", got, want) - } -} - -type BugD struct { // Same as BugA after tagging. - XXX string `json:"S"` -} - -// BugD's tagged S field should dominate BugA's. -type BugY struct { - BugA - BugD -} - -// Test that a field with a tag dominates untagged fields. -func TestTaggedFieldDominates(t *testing.T) { - v := BugY{ - BugA{"BugA"}, - BugD{"BugD"}, - } - b, err := Marshal(v) - if err != nil { - t.Fatal("Marshal:", err) - } - want := `{"S":"BugD"}` - got := string(b) - if got != want { - t.Fatalf("Marshal: got %s want %s", got, want) - } -} - -// There are no tags here, so S should not appear. -type BugZ struct { - BugA - BugC - BugY // Contains a tagged S field through BugD; should not dominate. -} - -func TestDuplicatedFieldDisappears(t *testing.T) { - v := BugZ{ - BugA{"BugA"}, - BugC{"BugC"}, - BugY{ - BugA{"nested BugA"}, - BugD{"nested BugD"}, - }, - } - b, err := Marshal(v) - if err != nil { - t.Fatal("Marshal:", err) - } - want := `{}` - got := string(b) - if got != want { - t.Fatalf("Marshal: got %s want %s", got, want) - } -} - -func TestStringBytes(t *testing.T) { - t.Parallel() - // Test that encodeState.stringBytes and encodeState.string use the same encoding. - var r []rune - for i := '\u0000'; i <= unicode.MaxRune; i++ { - if testing.Short() && i > 1000 { - i = unicode.MaxRune - } - r = append(r, i) - } - s := string(r) + "\xff\xff\xffhello" // some invalid UTF-8 too - - for _, escapeHTML := range []bool{true, false} { - es := &encodeState{} - es.string(s, escapeHTML) - - esBytes := &encodeState{} - esBytes.stringBytes([]byte(s), escapeHTML) - - enc := es.Buffer.String() - encBytes := esBytes.Buffer.String() - if enc != encBytes { - i := 0 - for i < len(enc) && i < len(encBytes) && enc[i] == encBytes[i] { - i++ - } - enc = enc[i:] - encBytes = encBytes[i:] - i = 0 - for i < len(enc) && i < len(encBytes) && enc[len(enc)-i-1] == encBytes[len(encBytes)-i-1] { - i++ - } - enc = enc[:len(enc)-i] - encBytes = encBytes[:len(encBytes)-i] - - if len(enc) > 20 { - enc = enc[:20] + "..." - } - if len(encBytes) > 20 { - encBytes = encBytes[:20] + "..." - } - - t.Errorf("with escapeHTML=%t, encodings differ at %#q vs %#q", - escapeHTML, enc, encBytes) - } - } -} - -func TestIssue10281(t *testing.T) { - type Foo struct { - N Number - } - x := Foo{Number(`invalid`)} - - b, err := Marshal(&x) - if err == nil { - t.Errorf("Marshal(&x) = %#q; want error", b) - } -} - -func TestHTMLEscape(t *testing.T) { - var b, want bytes.Buffer - m := `{"M":"foo &` + "\xe2\x80\xa8 \xe2\x80\xa9" + `"}` - want.Write([]byte(`{"M":"\u003chtml\u003efoo \u0026\u2028 \u2029\u003c/html\u003e"}`)) - HTMLEscape(&b, []byte(m)) - if !bytes.Equal(b.Bytes(), want.Bytes()) { - t.Errorf("HTMLEscape(&b, []byte(m)) = %s; want %s", b.Bytes(), want.Bytes()) - } -} - -// golang.org/issue/8582 -func TestEncodePointerString(t *testing.T) { - type stringPointer struct { - N *int64 `json:"n,string"` - } - var n int64 = 42 - b, err := Marshal(stringPointer{N: &n}) - if err != nil { - t.Fatalf("Marshal: %v", err) - } - if got, want := string(b), `{"n":"42"}`; got != want { - t.Errorf("Marshal = %s, want %s", got, want) - } - var back stringPointer - err = Unmarshal(b, &back) - if err != nil { - t.Fatalf("Unmarshal: %v", err) - } - if back.N == nil { - t.Fatalf("Unmarshaled nil N field") - } - if *back.N != 42 { - t.Fatalf("*N = %d; want 42", *back.N) - } -} - -var encodeStringTests = []struct { - in string - out string -}{ - {"\x00", `"\u0000"`}, - {"\x01", `"\u0001"`}, - {"\x02", `"\u0002"`}, - {"\x03", `"\u0003"`}, - {"\x04", `"\u0004"`}, - {"\x05", `"\u0005"`}, - {"\x06", `"\u0006"`}, - {"\x07", `"\u0007"`}, - {"\x08", `"\u0008"`}, - {"\x09", `"\t"`}, - {"\x0a", `"\n"`}, - {"\x0b", `"\u000b"`}, - {"\x0c", `"\u000c"`}, - {"\x0d", `"\r"`}, - {"\x0e", `"\u000e"`}, - {"\x0f", `"\u000f"`}, - {"\x10", `"\u0010"`}, - {"\x11", `"\u0011"`}, - {"\x12", `"\u0012"`}, - {"\x13", `"\u0013"`}, - {"\x14", `"\u0014"`}, - {"\x15", `"\u0015"`}, - {"\x16", `"\u0016"`}, - {"\x17", `"\u0017"`}, - {"\x18", `"\u0018"`}, - {"\x19", `"\u0019"`}, - {"\x1a", `"\u001a"`}, - {"\x1b", `"\u001b"`}, - {"\x1c", `"\u001c"`}, - {"\x1d", `"\u001d"`}, - {"\x1e", `"\u001e"`}, - {"\x1f", `"\u001f"`}, -} - -func TestEncodeString(t *testing.T) { - for _, tt := range encodeStringTests { - b, err := Marshal(tt.in) - if err != nil { - t.Errorf("Marshal(%q): %v", tt.in, err) - continue - } - out := string(b) - if out != tt.out { - t.Errorf("Marshal(%q) = %#q, want %#q", tt.in, out, tt.out) - } - } -} - -type jsonbyte byte - -func (b jsonbyte) MarshalJSON() ([]byte, error) { return tenc(`{"JB":%d}`, b) } - -type textbyte byte - -func (b textbyte) MarshalText() ([]byte, error) { return tenc(`TB:%d`, b) } - -type jsonint int - -func (i jsonint) MarshalJSON() ([]byte, error) { return tenc(`{"JI":%d}`, i) } - -type textint int - -func (i textint) MarshalText() ([]byte, error) { return tenc(`TI:%d`, i) } - -func tenc(format string, a ...any) ([]byte, error) { - var buf bytes.Buffer - fmt.Fprintf(&buf, format, a...) - return buf.Bytes(), nil -} - -type textfloat float64 - -func (f textfloat) MarshalText() ([]byte, error) { return tenc(`TF:%0.2f`, f) } - -// Issue 13783 -func TestEncodeBytekind(t *testing.T) { - t.Skip() // TODO - testdata := []struct { - data any - want string - }{ - {byte(7), "7"}, - {jsonbyte(7), `{"JB":7}`}, - {textbyte(4), `"TB:4"`}, - {jsonint(5), `{"JI":5}`}, - {textint(1), `"TI:1"`}, - {[]byte{0, 1}, `"AAE="`}, - {[]jsonbyte{0, 1}, `[{"JB":0},{"JB":1}]`}, - {[][]jsonbyte{{0, 1}, {3}}, `[[{"JB":0},{"JB":1}],[{"JB":3}]]`}, - {[]textbyte{2, 3}, `["TB:2","TB:3"]`}, - {[]jsonint{5, 4}, `[{"JI":5},{"JI":4}]`}, - {[]textint{9, 3}, `["TI:9","TI:3"]`}, - {[]int{9, 3}, `[9,3]`}, - {[]textfloat{12, 3}, `["TF:12.00","TF:3.00"]`}, - } - for _, d := range testdata { - js, err := Marshal(d.data) - if err != nil { - t.Error(err) - continue - } - got, want := string(js), d.want - if got != want { - t.Errorf("got %s, want %s", got, want) - } - } -} - -func TestTextMarshalerMapKeysAreSorted(t *testing.T) { - b, err := Marshal(map[unmarshalerText]int{ - {"x", "y"}: 1, - {"y", "x"}: 2, - {"a", "z"}: 3, - {"z", "a"}: 4, - }) - if err != nil { - t.Fatalf("Failed to Marshal text.Marshaler: %v", err) - } - const want = `{"a:z":3,"x:y":1,"y:x":2,"z:a":4}` - if string(b) != want { - t.Errorf("Marshal map with text.Marshaler keys: got %#q, want %#q", b, want) - } -} - -// https://golang.org/issue/33675 -func TestNilMarshalerTextMapKey(t *testing.T) { - b, err := Marshal(map[*unmarshalerText]int{ - (*unmarshalerText)(nil): 1, - {"A", "B"}: 2, - }) - if err != nil { - t.Fatalf("Failed to Marshal *text.Marshaler: %v", err) - } - const want = `{"":1,"A:B":2}` - if string(b) != want { - t.Errorf("Marshal map with *text.Marshaler keys: got %#q, want %#q", b, want) - } -} - -var re = regexp.MustCompile - -// syntactic checks on form of marshaled floating point numbers. -var badFloatREs = []*regexp.Regexp{ - re(`p`), // no binary exponential notation - re(`^\+`), // no leading + sign - re(`^-?0[^.]`), // no unnecessary leading zeros - re(`^-?\.`), // leading zero required before decimal point - re(`\.(e|$)`), // no trailing decimal - re(`\.[0-9]+0(e|$)`), // no trailing zero in fraction - re(`^-?(0|[0-9]{2,})\..*e`), // exponential notation must have normalized mantissa - re(`e[0-9]`), // positive exponent must be signed - re(`e[+-]0`), // exponent must not have leading zeros - re(`e-[1-6]$`), // not tiny enough for exponential notation - re(`e+(.|1.|20)$`), // not big enough for exponential notation - re(`^-?0\.0000000`), // too tiny, should use exponential notation - re(`^-?[0-9]{22}`), // too big, should use exponential notation - re(`[1-9][0-9]{16}[1-9]`), // too many significant digits in integer - re(`[1-9][0-9.]{17}[1-9]`), // too many significant digits in decimal - // below here for float32 only - re(`[1-9][0-9]{8}[1-9]`), // too many significant digits in integer - re(`[1-9][0-9.]{9}[1-9]`), // too many significant digits in decimal -} - -func TestMarshalFloat(t *testing.T) { - t.Parallel() - nfail := 0 - test := func(f float64, bits int) { - vf := any(f) - if bits == 32 { - f = float64(float32(f)) // round - vf = float32(f) - } - bout, err := Marshal(vf) - if err != nil { - t.Errorf("Marshal(%T(%g)): %v", vf, vf, err) - nfail++ - return - } - out := string(bout) - - // result must convert back to the same float - g, err := strconv.ParseFloat(out, bits) - if err != nil { - t.Errorf("Marshal(%T(%g)) = %q, cannot parse back: %v", vf, vf, out, err) - nfail++ - return - } - if f != g || fmt.Sprint(f) != fmt.Sprint(g) { // fmt.Sprint handles ±0 - t.Errorf("Marshal(%T(%g)) = %q (is %g, not %g)", vf, vf, out, float32(g), vf) - nfail++ - return - } - - bad := badFloatREs - if bits == 64 { - bad = bad[:len(bad)-2] - } - for _, re := range bad { - if re.MatchString(out) { - t.Errorf("Marshal(%T(%g)) = %q, must not match /%s/", vf, vf, out, re) - nfail++ - return - } - } - } - - var ( - bigger = math.Inf(+1) - smaller = math.Inf(-1) - ) - - var digits = "1.2345678901234567890123" - for i := len(digits); i >= 2; i-- { - if testing.Short() && i < len(digits)-4 { - break - } - for exp := -30; exp <= 30; exp++ { - for _, sign := range "+-" { - for bits := 32; bits <= 64; bits += 32 { - s := fmt.Sprintf("%c%se%d", sign, digits[:i], exp) - f, err := strconv.ParseFloat(s, bits) - if err != nil { - log.Fatal(err) - } - next := math.Nextafter - if bits == 32 { - next = func(g, h float64) float64 { - return float64(math.Nextafter32(float32(g), float32(h))) - } - } - test(f, bits) - test(next(f, bigger), bits) - test(next(f, smaller), bits) - if nfail > 50 { - t.Fatalf("stopping test early") - } - } - } - } - } - test(0, 64) - test(math.Copysign(0, -1), 64) - test(0, 32) - test(math.Copysign(0, -1), 32) -} - -func TestMarshalRawMessageValue(t *testing.T) { - type ( - T1 struct { - M RawMessage `json:",omitempty"` - } - T2 struct { - M *RawMessage `json:",omitempty"` - } - ) - - var ( - rawNil = RawMessage(nil) - rawEmpty = RawMessage([]byte{}) - rawText = RawMessage([]byte(`"foo"`)) - ) - - tests := []struct { - in any - want string - ok bool - }{ - // Test with nil RawMessage. - {rawNil, "null", true}, - {&rawNil, "null", true}, - {[]any{rawNil}, "[null]", true}, - {&[]any{rawNil}, "[null]", true}, - {[]any{&rawNil}, "[null]", true}, - {&[]any{&rawNil}, "[null]", true}, - {struct{ M RawMessage }{rawNil}, `{"M":null}`, true}, - {&struct{ M RawMessage }{rawNil}, `{"M":null}`, true}, - {struct{ M *RawMessage }{&rawNil}, `{"M":null}`, true}, - {&struct{ M *RawMessage }{&rawNil}, `{"M":null}`, true}, - {map[string]any{"M": rawNil}, `{"M":null}`, true}, - {&map[string]any{"M": rawNil}, `{"M":null}`, true}, - {map[string]any{"M": &rawNil}, `{"M":null}`, true}, - {&map[string]any{"M": &rawNil}, `{"M":null}`, true}, - {T1{rawNil}, "{}", true}, - {T2{&rawNil}, `{"M":null}`, true}, - {&T1{rawNil}, "{}", true}, - {&T2{&rawNil}, `{"M":null}`, true}, - - // Test with empty, but non-nil, RawMessage. - {rawEmpty, "", false}, - {&rawEmpty, "", false}, - {[]any{rawEmpty}, "", false}, - {&[]any{rawEmpty}, "", false}, - {[]any{&rawEmpty}, "", false}, - {&[]any{&rawEmpty}, "", false}, - {struct{ X RawMessage }{rawEmpty}, "", false}, - {&struct{ X RawMessage }{rawEmpty}, "", false}, - {struct{ X *RawMessage }{&rawEmpty}, "", false}, - {&struct{ X *RawMessage }{&rawEmpty}, "", false}, - {map[string]any{"nil": rawEmpty}, "", false}, - {&map[string]any{"nil": rawEmpty}, "", false}, - {map[string]any{"nil": &rawEmpty}, "", false}, - {&map[string]any{"nil": &rawEmpty}, "", false}, - {T1{rawEmpty}, "{}", true}, - {T2{&rawEmpty}, "", false}, - {&T1{rawEmpty}, "{}", true}, - {&T2{&rawEmpty}, "", false}, - - // Test with RawMessage with some text. - // - // The tests below marked with Issue6458 used to generate "ImZvbyI=" instead "foo". - // This behavior was intentionally changed in Go 1.8. - // See https://golang.org/issues/14493#issuecomment-255857318 - {rawText, `"foo"`, true}, // Issue6458 - {&rawText, `"foo"`, true}, - {[]any{rawText}, `["foo"]`, true}, // Issue6458 - {&[]any{rawText}, `["foo"]`, true}, // Issue6458 - {[]any{&rawText}, `["foo"]`, true}, - {&[]any{&rawText}, `["foo"]`, true}, - {struct{ M RawMessage }{rawText}, `{"M":"foo"}`, true}, // Issue6458 - {&struct{ M RawMessage }{rawText}, `{"M":"foo"}`, true}, - {struct{ M *RawMessage }{&rawText}, `{"M":"foo"}`, true}, - {&struct{ M *RawMessage }{&rawText}, `{"M":"foo"}`, true}, - {map[string]any{"M": rawText}, `{"M":"foo"}`, true}, // Issue6458 - {&map[string]any{"M": rawText}, `{"M":"foo"}`, true}, // Issue6458 - {map[string]any{"M": &rawText}, `{"M":"foo"}`, true}, - {&map[string]any{"M": &rawText}, `{"M":"foo"}`, true}, - {T1{rawText}, `{"M":"foo"}`, true}, // Issue6458 - {T2{&rawText}, `{"M":"foo"}`, true}, - {&T1{rawText}, `{"M":"foo"}`, true}, - {&T2{&rawText}, `{"M":"foo"}`, true}, - } - - for i, tt := range tests { - b, err := Marshal(tt.in) - if ok := (err == nil); ok != tt.ok { - if err != nil { - t.Errorf("test %d, unexpected failure: %v", i, err) - } else { - t.Skip() // MODIFIED - t.Errorf("test %d, unexpected success", i) - } - } - if got := string(b); got != tt.want { - t.Errorf("test %d, Marshal(%#v) = %q, want %q", i, tt.in, got, tt.want) - } - } -} - -type marshalPanic struct{} - -func (marshalPanic) MarshalJSON() ([]byte, error) { panic(0xdead) } - -func TestMarshalPanic(t *testing.T) { - defer func() { - if got := recover(); !reflect.DeepEqual(got, 0xdead) { - t.Errorf("panic() = (%T)(%v), want 0xdead", got, got) - } - }() - _, _ = Marshal(&marshalPanic{}) // MODIFIED - t.Error("Marshal should have panicked") -} - -func TestMarshalUncommonFieldNames(t *testing.T) { - v := struct { - A0, À, Aβ int - }{} - b, err := Marshal(v) - if err != nil { - t.Fatal("Marshal:", err) - } - want := `{"A0":0,"À":0,"Aβ":0}` - got := string(b) - if got != want { - t.Fatalf("Marshal: got %s want %s", got, want) - } -} - -/* // MODIFIED -func TestMarshalerError(t *testing.T) { - s := "test variable" - st := reflect.TypeOf(s) - errText := "json: test error" - - tests := []struct { - err *MarshalerError - want string - }{ - { - &MarshalerError{st, fmt.Errorf(errText), ""}, - "json: error calling MarshalJSON for type " + st.String() + ": " + errText, - }, - { - &MarshalerError{st, fmt.Errorf(errText), "TestMarshalerError"}, - "json: error calling TestMarshalerError for type " + st.String() + ": " + errText, - }, - } - - for i, tt := range tests { - got := tt.err.Error() - if got != tt.want { - t.Errorf("MarshalerError test %d, got: %s, want: %s", i, got, tt.want) - } - } -} -*/ // MODIFIED diff --git a/lib/lowmemjson/borrowed_fuzz_test.go b/lib/lowmemjson/borrowed_fuzz_test.go deleted file mode 100644 index 21756a7..0000000 --- a/lib/lowmemjson/borrowed_fuzz_test.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2021 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package lowmemjson // MODIFIED - -import ( - "testing" -) - -func FuzzUnmarshalJSON(f *testing.F) { - f.Add([]byte(`{ -"object": { - "slice": [ - 1, - 2.0, - "3", - [4], - {5: {}} - ] -}, -"slice": [[]], -"string": ":)", -"int": 1e5, -"float": 3e-9" -}`)) - - f.Fuzz(func(t *testing.T, b []byte) { - for _, typ := range []func() interface{}{ - func() interface{} { return new(interface{}) }, - func() interface{} { return new(map[string]interface{}) }, - func() interface{} { return new([]interface{}) }, - } { - i := typ() - if err := Unmarshal(b, i); err != nil { - return - } - - encoded, err := Marshal(i) - if err != nil { - t.Fatalf("failed to marshal: %s", err) - } - - if err := Unmarshal(encoded, i); err != nil { - t.Fatalf("failed to roundtrip: %s", err) - } - } - }) -} - -/* // MODIFIED -func FuzzDecoderToken(f *testing.F) { - f.Add([]byte(`{ -"object": { - "slice": [ - 1, - 2.0, - "3", - [4], - {5: {}} - ] -}, -"slice": [[]], -"string": ":)", -"int": 1e5, -"float": 3e-9" -}`)) - - f.Fuzz(func(t *testing.T, b []byte) { - r := bytes.NewReader(b) - d := NewDecoder(r) - for { - _, err := d.Token() - if err != nil { - if err == io.EOF { - break - } - return - } - } - }) -} -*/ // MODIFIED diff --git a/lib/lowmemjson/borrowed_misc.go b/lib/lowmemjson/borrowed_misc.go deleted file mode 100644 index 5c6bbb6..0000000 --- a/lib/lowmemjson/borrowed_misc.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package lowmemjson - -import ( - "fmt" - "reflect" -) - -// A SyntaxError is a description of a JSON syntax error. -// -// from scanner.go -type SyntaxError struct { - msg string // description of error - Offset int64 // error occurred after reading Offset bytes -} - -func (e *SyntaxError) Error() string { - return fmt.Sprintf("JSON syntax error at input byte %v: %v", - e.Offset, e.msg) -} - -// from encode.go -func isEmptyValue(v reflect.Value) bool { - switch v.Kind() { - case reflect.Array, reflect.Map, reflect.Slice, reflect.String: - return v.Len() == 0 - case reflect.Bool: - return !v.Bool() - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return v.Int() == 0 - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return v.Uint() == 0 - case reflect.Float32, reflect.Float64: - return v.Float() == 0 - case reflect.Interface, reflect.Pointer: - return v.IsNil() - } - return false -} diff --git a/lib/lowmemjson/borrowed_scanner_test.go b/lib/lowmemjson/borrowed_scanner_test.go deleted file mode 100644 index c5d67e6..0000000 --- a/lib/lowmemjson/borrowed_scanner_test.go +++ /dev/null @@ -1,305 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package lowmemjson // MODIFIED - -import ( - "bytes" - "math" - "math/rand" - "reflect" - "testing" -) - -var validTests = []struct { - data string - ok bool -}{ - {`foo`, false}, - {`}{`, false}, - {`{]`, false}, - {`{}`, true}, - {`{"foo":"bar"}`, true}, - {`{"foo":"bar","bar":{"baz":["qux"]}}`, true}, -} - -func TestValid(t *testing.T) { - for _, tt := range validTests { - if ok := Valid([]byte(tt.data)); ok != tt.ok { - t.Errorf("Valid(%#q) = %v, want %v", tt.data, ok, tt.ok) - } - } -} - -// Tests of simple examples. - -type example struct { - compact string - indent string -} - -var examples = []example{ - {`1`, `1`}, - {`{}`, `{}`}, - {`[]`, `[]`}, - {`{"":2}`, "{\n\t\"\": 2\n}"}, - {`[3]`, "[\n\t3\n]"}, - {`[1,2,3]`, "[\n\t1,\n\t2,\n\t3\n]"}, - {`{"x":1}`, "{\n\t\"x\": 1\n}"}, - {ex1, ex1i}, - {"{\"\":\"<>&\u2028\u2029\"}", "{\n\t\"\": \"<>&\u2028\u2029\"\n}"}, // See golang.org/issue/34070 -} - -var ex1 = `[true,false,null,"x",1,1.5,0,-5e+2]` - -var ex1i = `[ - true, - false, - null, - "x", - 1, - 1.5, - 0, - -5e+2 -]` - -func TestCompact(t *testing.T) { - t.Skip() // TODO - var buf bytes.Buffer - for _, tt := range examples { - buf.Reset() - if err := Compact(&buf, []byte(tt.compact)); err != nil { - t.Errorf("Compact(%#q): %v", tt.compact, err) - } else if s := buf.String(); s != tt.compact { - t.Errorf("Compact(%#q) = %#q, want original", tt.compact, s) - } - - buf.Reset() - if err := Compact(&buf, []byte(tt.indent)); err != nil { - t.Errorf("Compact(%#q): %v", tt.indent, err) - continue - } else if s := buf.String(); s != tt.compact { - t.Errorf("Compact(%#q) = %#q, want %#q", tt.indent, s, tt.compact) - } - } -} - -func TestCompactSeparators(t *testing.T) { - t.Skip() // TODO - // U+2028 and U+2029 should be escaped inside strings. - // They should not appear outside strings. - tests := []struct { - in, compact string - }{ - {"{\"\u2028\": 1}", "{\"\u2028\":1}"}, - {"{\"\u2029\" :2}", "{\"\u2029\":2}"}, - } - for _, tt := range tests { - var buf bytes.Buffer - if err := Compact(&buf, []byte(tt.in)); err != nil { - t.Errorf("Compact(%q): %v", tt.in, err) - } else if s := buf.String(); s != tt.compact { - t.Errorf("Compact(%q) = %q, want %q", tt.in, s, tt.compact) - } - } -} - -func TestIndent(t *testing.T) { - t.Skip() // TODO - var buf bytes.Buffer - for _, tt := range examples { - buf.Reset() - if err := Indent(&buf, []byte(tt.indent), "", "\t"); err != nil { - t.Errorf("Indent(%#q): %v", tt.indent, err) - } else if s := buf.String(); s != tt.indent { - t.Errorf("Indent(%#q) = %#q, want original", tt.indent, s) - } - - buf.Reset() - if err := Indent(&buf, []byte(tt.compact), "", "\t"); err != nil { - t.Errorf("Indent(%#q): %v", tt.compact, err) - continue - } else if s := buf.String(); s != tt.indent { - t.Errorf("Indent(%#q) = %#q, want %#q", tt.compact, s, tt.indent) - } - } -} - -// Tests of a large random structure. - -func TestCompactBig(t *testing.T) { - initBig() - var buf bytes.Buffer - if err := Compact(&buf, jsonBig); err != nil { - t.Fatalf("Compact: %v", err) - } - b := buf.Bytes() - if !bytes.Equal(b, jsonBig) { - t.Error("Compact(jsonBig) != jsonBig") - diff(t, b, jsonBig) - return - } -} - -func TestIndentBig(t *testing.T) { - t.Parallel() - initBig() - var buf bytes.Buffer - if err := Indent(&buf, jsonBig, "", "\t"); err != nil { - t.Fatalf("Indent1: %v", err) - } - b := buf.Bytes() - if len(b) == len(jsonBig) { - // jsonBig is compact (no unnecessary spaces); - // indenting should make it bigger - t.Fatalf("Indent(jsonBig) did not get bigger") - } - - // should be idempotent - var buf1 bytes.Buffer - if err := Indent(&buf1, b, "", "\t"); err != nil { - t.Fatalf("Indent2: %v", err) - } - b1 := buf1.Bytes() - if !bytes.Equal(b1, b) { - t.Error("Indent(Indent(jsonBig)) != Indent(jsonBig)") - diff(t, b1, b) - return - } - - // should get back to original - buf1.Reset() - if err := Compact(&buf1, b); err != nil { - t.Fatalf("Compact: %v", err) - } - b1 = buf1.Bytes() - if !bytes.Equal(b1, jsonBig) { - t.Error("Compact(Indent(jsonBig)) != jsonBig") - diff(t, b1, jsonBig) - return - } -} - -type indentErrorTest struct { - in string - err error -} - -var indentErrorTests = []indentErrorTest{ - {`{"X": "foo", "Y"}`, &SyntaxError{"invalid character '}' after object key", 17}}, - {`{"X": "foo" "Y": "bar"}`, &SyntaxError{"invalid character '\"' after object key:value pair", 13}}, -} - -func TestIndentErrors(t *testing.T) { - t.Skip() // TODO - for i, tt := range indentErrorTests { - slice := make([]uint8, 0) - buf := bytes.NewBuffer(slice) - if err := Indent(buf, []uint8(tt.in), "", ""); err != nil { - if !reflect.DeepEqual(err, tt.err) { - t.Errorf("#%d: Indent: %#v", i, err) - continue - } - } - } -} - -func diff(t *testing.T, a, b []byte) { - for i := 0; ; i++ { - if i >= len(a) || i >= len(b) || a[i] != b[i] { - j := i - 10 - if j < 0 { - j = 0 - } - t.Errorf("diverge at %d: «%s» vs «%s»", i, trim(a[j:]), trim(b[j:])) - return - } - } -} - -func trim(b []byte) []byte { - if len(b) > 20 { - return b[0:20] - } - return b -} - -// Generate a random JSON object. - -var jsonBig []byte - -func initBig() { - n := 10000 - if testing.Short() { - n = 100 - } - b, err := Marshal(genValue(n)) - if err != nil { - panic(err) - } - jsonBig = b -} - -func genValue(n int) any { - if n > 1 { - switch rand.Intn(2) { - case 0: - return genArray(n) - case 1: - return genMap(n) - } - } - switch rand.Intn(3) { - case 0: - return rand.Intn(2) == 0 - case 1: - return rand.NormFloat64() - case 2: - return genString(30) - } - panic("unreachable") -} - -func genString(stddev float64) string { - n := int(math.Abs(rand.NormFloat64()*stddev + stddev/2)) - c := make([]rune, n) - for i := range c { - f := math.Abs(rand.NormFloat64()*64 + 32) - if f > 0x10ffff { - f = 0x10ffff - } - c[i] = rune(f) - } - return string(c) -} - -func genArray(n int) []any { - f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2))) - if f > n { - f = n - } - if f < 1 { - f = 1 - } - x := make([]any, f) - for i := range x { - x[i] = genValue(((i+1)*n)/f - (i*n)/f) - } - return x -} - -func genMap(n int) map[string]any { - f := int(math.Abs(rand.NormFloat64()) * math.Min(10, float64(n/2))) - if f > n { - f = n - } - if n > 0 && f == 0 { - f = 1 - } - x := make(map[string]any) - for i := 0; i < f; i++ { - x[genString(10)] = genValue(((i+1)*n)/f - (i*n)/f) - } - return x -} diff --git a/lib/lowmemjson/borrowed_tagkey_test.go b/lib/lowmemjson/borrowed_tagkey_test.go deleted file mode 100644 index 2d4d2c0..0000000 --- a/lib/lowmemjson/borrowed_tagkey_test.go +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package lowmemjson // MODIFIED - -import ( - "testing" -) - -type basicLatin2xTag struct { - V string `json:"$%-/"` -} - -type basicLatin3xTag struct { - V string `json:"0123456789"` -} - -type basicLatin4xTag struct { - V string `json:"ABCDEFGHIJKLMO"` -} - -type basicLatin5xTag struct { - V string `json:"PQRSTUVWXYZ_"` -} - -type basicLatin6xTag struct { - V string `json:"abcdefghijklmno"` -} - -type basicLatin7xTag struct { - V string `json:"pqrstuvwxyz"` -} - -type miscPlaneTag struct { - V string `json:"色は匂へど"` -} - -type percentSlashTag struct { - V string `json:"text/html%"` // https://golang.org/issue/2718 -} - -type punctuationTag struct { - V string `json:"!#$%&()*+-./:;<=>?@[]^_{|}~ "` // https://golang.org/issue/3546 -} - -type dashTag struct { - V string `json:"-,"` -} - -type emptyTag struct { - W string -} - -type misnamedTag struct { - X string `jsom:"Misnamed"` -} - -type badFormatTag struct { - Y string `:"BadFormat"` -} - -type badCodeTag struct { - Z string `json:" !\"#&'()*+,."` -} - -type spaceTag struct { - Q string `json:"With space"` -} - -type unicodeTag struct { - W string `json:"Ελλάδα"` -} - -var structTagObjectKeyTests = []struct { - raw any - value string - key string -}{ - {basicLatin2xTag{"2x"}, "2x", "$%-/"}, - {basicLatin3xTag{"3x"}, "3x", "0123456789"}, - {basicLatin4xTag{"4x"}, "4x", "ABCDEFGHIJKLMO"}, - {basicLatin5xTag{"5x"}, "5x", "PQRSTUVWXYZ_"}, - {basicLatin6xTag{"6x"}, "6x", "abcdefghijklmno"}, - {basicLatin7xTag{"7x"}, "7x", "pqrstuvwxyz"}, - {miscPlaneTag{"いろはにほへと"}, "いろはにほへと", "色は匂へど"}, - {dashTag{"foo"}, "foo", "-"}, - {emptyTag{"Pour Moi"}, "Pour Moi", "W"}, - {misnamedTag{"Animal Kingdom"}, "Animal Kingdom", "X"}, - {badFormatTag{"Orfevre"}, "Orfevre", "Y"}, - {badCodeTag{"Reliable Man"}, "Reliable Man", "Z"}, - {percentSlashTag{"brut"}, "brut", "text/html%"}, - {punctuationTag{"Union Rags"}, "Union Rags", "!#$%&()*+-./:;<=>?@[]^_{|}~ "}, - {spaceTag{"Perreddu"}, "Perreddu", "With space"}, - {unicodeTag{"Loukanikos"}, "Loukanikos", "Ελλάδα"}, -} - -func TestStructTagObjectKey(t *testing.T) { - t.Skip() // TODO - for _, tt := range structTagObjectKeyTests { - b, err := Marshal(tt.raw) - if err != nil { - t.Fatalf("Marshal(%#q) failed: %v", tt.raw, err) - } - var f any - err = Unmarshal(b, &f) - if err != nil { - t.Fatalf("Unmarshal(%#q) failed: %v", b, err) - } - for i, v := range f.(map[string]any) { - switch i { - case tt.key: - if s, ok := v.(string); !ok || s != tt.value { - t.Fatalf("Unexpected value: %#q, want %v", s, tt.value) - } - default: - t.Fatalf("Unexpected key: %#q, from %#q", i, b) - } - } - } -} diff --git a/lib/lowmemjson/borrowed_tags.go b/lib/lowmemjson/borrowed_tags.go deleted file mode 100644 index 07292b1..0000000 --- a/lib/lowmemjson/borrowed_tags.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package lowmemjson - -import ( - "strings" -) - -// tagOptions is the string following a comma in a struct field's "json" -// tag, or the empty string. It does not include the leading comma. -type tagOptions string - -// parseTag splits a struct field's json tag into its name and -// comma-separated options. -func parseTag(tag string) (string, tagOptions) { - tag, opt, _ := strings.Cut(tag, ",") - return tag, tagOptions(opt) -} - -// Contains reports whether a comma-separated list of options -// contains a particular substr flag. substr must be surrounded by a -// string boundary or commas. -func (o tagOptions) Contains(optionName string) bool { - if len(o) == 0 { - return false - } - s := string(o) - for s != "" { - var name string - name, s, _ = strings.Cut(s, ",") - if name == optionName { - return true - } - } - return false -} diff --git a/lib/lowmemjson/borrowed_tags_test.go b/lib/lowmemjson/borrowed_tags_test.go deleted file mode 100644 index 28d76e9..0000000 --- a/lib/lowmemjson/borrowed_tags_test.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package lowmemjson // MODIFIED - -import ( - "testing" -) - -func TestTagParsing(t *testing.T) { - name, opts := parseTag("field,foobar,foo") - if name != "field" { - t.Fatalf("name = %q, want field", name) - } - for _, tt := range []struct { - opt string - want bool - }{ - {"foobar", true}, - {"foo", true}, - {"bar", false}, - } { - if opts.Contains(tt.opt) != tt.want { - t.Errorf("Contains(%q) = %v", tt.opt, !tt.want) - } - } -} diff --git a/lib/lowmemjson/decode.go b/lib/lowmemjson/decode.go deleted file mode 100644 index 03d5b7a..0000000 --- a/lib/lowmemjson/decode.go +++ /dev/null @@ -1,812 +0,0 @@ -// Copyright (C) 2022 Luke Shumaker -// -// SPDX-License-Identifier: GPL-2.0-or-later - -package lowmemjson - -import ( - "bufio" - "bytes" - "encoding" - "encoding/json" - "fmt" - "io" - "reflect" - "strconv" - "strings" -) - -type Decodable interface { - DecodeJSON(io.RuneScanner) error -} - -type runeBuffer interface { - io.Writer - WriteRune(rune) (int, error) - Reset() -} - -type Decoder struct { - r io.RuneScanner - - // config - disallowUnknownFields bool - useNumber bool - - // state - err error - curPos int64 - nxtPos int64 - stack []any -} - -var forceBufio bool - -func NewDecoder(r io.Reader) *Decoder { - rs, ok := r.(io.RuneScanner) - if forceBufio || !ok { - rs = bufio.NewReader(r) - } - return &Decoder{ - r: rs, - } -} - -func (dec *Decoder) DisallowUnknownFields() { dec.disallowUnknownFields = true } -func (dec *Decoder) UseNumber() { dec.useNumber = true } -func (dec *Decoder) InputOffset() int64 { return dec.curPos } - -func (dec *Decoder) More() bool { - dec.decodeWS() - _, ok := dec.peekRuneOrEOF() - return ok -} - -func (dec *Decoder) stackStr() string { - var buf strings.Builder - buf.WriteString("v") - for _, item := range dec.stack { - fmt.Fprintf(&buf, "[%#v]", item) - } - return buf.String() -} - -func (dec *Decoder) stackPush(idx any) { - dec.stack = append(dec.stack, idx) -} -func (dec *Decoder) stackPop() { - dec.stack = dec.stack[:len(dec.stack)-1] -} - -type decodeError struct { - Err error -} - -func (dec *Decoder) panicIO(err error) { - panic(decodeError{fmt.Errorf("json: I/O error at input byte %v: %s: %w", - dec.nxtPos, dec.stackStr(), err)}) -} -func (dec *Decoder) panicSyntax(err error) { - panic(decodeError{fmt.Errorf("json: syntax error at input byte %v: %s: %w", - dec.curPos, dec.stackStr(), err)}) -} -func (dec *Decoder) panicType(typ reflect.Type, err error) { - panic(decodeError{fmt.Errorf("json: type mismatch error at input byte %v: %s: type %v: %w", - dec.curPos, dec.stackStr(), typ, err)}) -} - -func Decode(r io.Reader, ptr any) error { - return NewDecoder(r).Decode(ptr) -} - -func (dec *Decoder) Decode(ptr any) (err error) { - ptrVal := reflect.ValueOf(ptr) - if ptrVal.Kind() != reflect.Pointer || ptrVal.IsNil() || !ptrVal.Elem().CanSet() { - return &json.InvalidUnmarshalError{ - // don't use ptrVal.Type() because ptrVal might be invalid if ptr==nil - Type: reflect.TypeOf(ptr), - } - } - - if dec.err != nil { - return dec.err - } - - defer func() { - if r := recover(); r != nil { - if de, ok := r.(decodeError); ok { - dec.err = de.Err - err = dec.err - } else { - panic(r) - } - } - }() - dec.decodeWS() - dec.decode(ptrVal.Elem(), false) - return nil -} - -func (dec *Decoder) readRune() rune { - c, size, err := dec.r.ReadRune() - if err != nil { - if err == io.EOF { - dec.panicSyntax(io.ErrUnexpectedEOF) - } - dec.panicIO(err) - } - dec.curPos = dec.nxtPos - dec.nxtPos = dec.curPos + int64(size) - return c -} - -func (dec *Decoder) readRuneOrEOF() (c rune, ok bool) { - c, size, err := dec.r.ReadRune() - if err != nil { - if err == io.EOF { - return 0, false - } - dec.panicIO(err) - } - dec.curPos = dec.nxtPos - dec.nxtPos = dec.curPos + int64(size) - return c, true -} - -func (dec *Decoder) unreadRune() { - if err := dec.r.UnreadRune(); err != nil { - // .UnreadRune() must succeed if the previous call was - // .ReadRune(), which it always is for this code. - panic(err) - } - dec.nxtPos = dec.curPos -} - -func (dec *Decoder) peekRune() rune { - c, _, err := dec.r.ReadRune() - if err != nil { - if err == io.EOF { - dec.panicSyntax(io.ErrUnexpectedEOF) - } - dec.panicIO(err) - } - if err := dec.r.UnreadRune(); err != nil { - // .UnreadRune() must succeed if the previous call was - // .ReadRune(), which it always is for this code. - panic(err) - } - return c -} - -func (dec *Decoder) peekRuneOrEOF() (rune, bool) { - c, _, err := dec.r.ReadRune() - if err != nil { - if err == io.EOF { - return 0, false - } - dec.panicIO(err) - } - if err := dec.r.UnreadRune(); err != nil { - // .UnreadRune() must succeed if the previous call was - // .ReadRune(), which it always is for this code. - panic(err) - } - return c, true -} - -func (dec *Decoder) expectRune(exp rune) { - act := dec.readRune() - if act != exp { - dec.panicSyntax(fmt.Errorf("expected %q but got %q", exp, act)) - } -} - -var ( - rawMessagePtrType = reflect.TypeOf((*json.RawMessage)(nil)) - decodableType = reflect.TypeOf((*Decodable)(nil)).Elem() - jsonUnmarshalerType = reflect.TypeOf((*json.Unmarshaler)(nil)).Elem() - textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() -) - -var kind2bits = map[reflect.Kind]int{ - reflect.Int: int(32 << (^uint(0) >> 63)), - reflect.Int8: 8, - reflect.Int16: 16, - reflect.Int32: 32, - reflect.Int64: 64, - - reflect.Uint: int(32 << (^uint(0) >> 63)), - reflect.Uint8: 8, - reflect.Uint16: 16, - reflect.Uint32: 32, - reflect.Uint64: 64, - - reflect.Uintptr: int(32 << (^uintptr(0) >> 63)), - - reflect.Float32: 32, - reflect.Float64: 64, -} - -func (dec *Decoder) decode(val reflect.Value, nullOK bool) { - typ := val.Type() - switch { - case val.CanAddr() && reflect.PointerTo(typ) == rawMessagePtrType: - var buf bytes.Buffer - dec.scan(&buf) - if err := val.Addr().Interface().(*json.RawMessage).UnmarshalJSON(buf.Bytes()); err != nil { - dec.panicSyntax(err) - } - case val.CanAddr() && reflect.PointerTo(typ).Implements(decodableType): - obj := val.Addr().Interface().(Decodable) - if err := obj.DecodeJSON(dec.r); err != nil { - dec.panicSyntax(err) - } - case val.CanAddr() && reflect.PointerTo(typ).Implements(jsonUnmarshalerType): - var buf bytes.Buffer - dec.scan(&buf) - obj := val.Addr().Interface().(json.Unmarshaler) - if err := obj.UnmarshalJSON(buf.Bytes()); err != nil { - dec.panicSyntax(err) - } - case val.CanAddr() && reflect.PointerTo(typ).Implements(textUnmarshalerType): - if nullOK && dec.peekRune() == 'n' { - dec.decodeNull() - return - } - var buf bytes.Buffer - dec.decodeString(&buf) - obj := val.Addr().Interface().(encoding.TextUnmarshaler) - if err := obj.UnmarshalText(buf.Bytes()); err != nil { - dec.panicSyntax(err) - } - default: - kind := typ.Kind() - switch kind { - case reflect.Bool: - if nullOK && dec.peekRune() == 'n' { - dec.decodeNull() - return - } - val.SetBool(dec.decodeBool()) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - if nullOK && dec.peekRune() == 'n' { - dec.decodeNull() - return - } - var buf strings.Builder - dec.scanNumber(&buf) - n, err := strconv.ParseInt(buf.String(), 10, kind2bits[kind]) - if err != nil { - dec.panicSyntax(err) - } - val.SetInt(n) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - if nullOK && dec.peekRune() == 'n' { - dec.decodeNull() - return - } - var buf strings.Builder - dec.scanNumber(&buf) - n, err := strconv.ParseUint(buf.String(), 10, kind2bits[kind]) - if err != nil { - dec.panicSyntax(err) - } - val.SetUint(n) - case reflect.Float32, reflect.Float64: - if nullOK && dec.peekRune() == 'n' { - dec.decodeNull() - return - } - var buf strings.Builder - dec.scanNumber(&buf) - n, err := strconv.ParseFloat(buf.String(), kind2bits[kind]) - if err != nil { - dec.panicSyntax(err) - } - val.SetFloat(n) - case reflect.String: - if nullOK && dec.peekRune() == 'n' { - dec.decodeNull() - return - } - var buf strings.Builder - if typ == numberType { - dec.scanNumber(&buf) - val.SetString(buf.String()) - } else { - dec.decodeString(&buf) - val.SetString(buf.String()) - } - case reflect.Interface: - if typ.NumMethod() > 0 { - dec.panicType(typ, fmt.Errorf("cannot decode in to non-empty interface")) - } - switch dec.peekRune() { - case 'n': - if !val.IsNil() && val.Elem().Kind() == reflect.Pointer && val.Elem().Elem().Kind() == reflect.Pointer { - // XXX: I can't justify this case, other than "it's what encoding/json does, but - // I don't understand their rationale". - dec.decode(val.Elem(), false) - } else { - dec.decodeNull() - val.Set(reflect.Zero(typ)) - } - default: - if !val.IsNil() && val.Elem().Kind() == reflect.Pointer { - dec.decode(val.Elem(), false) - } else { - val.Set(reflect.ValueOf(dec.decodeAny())) - } - } - case reflect.Struct: - if nullOK && dec.peekRune() == 'n' { - dec.decodeNull() - return - } - index := indexStruct(typ) - var nameBuf strings.Builder - dec.decodeObject(&nameBuf, func() { - name := nameBuf.String() - dec.stackPush(name) - defer dec.stackPop() - idx, ok := index.byName[name] - if !ok { - if dec.disallowUnknownFields { - dec.panicType(typ, fmt.Errorf("unknown field %q", name)) - } - dec.scan(io.Discard) - return - } - field := index.byPos[idx] - fVal := val - for _, idx := range field.Path { - if fVal.Kind() == reflect.Pointer { - if fVal.IsNil() { - if !fVal.CanSet() { // https://golang.org/issue/21357 - dec.panicType(fVal.Type().Elem(), fmt.Errorf("cannot set embedded pointer to unexported type")) - } - fVal.Set(reflect.New(fVal.Type().Elem())) - } - fVal = fVal.Elem() - } - fVal = fVal.Field(idx) - } - if field.Quote { - switch dec.peekRune() { - case 'n': - dec.decodeNull() - switch fVal.Kind() { - // XXX: I can't justify this list, other than "it's what encoding/json - // does, but I don't understand their rationale". - case reflect.Interface, reflect.Pointer, reflect.Map, reflect.Slice: - fVal.Set(reflect.Zero(fVal.Type())) - } - case '"': - // TODO: Figure out how to do this without buffering. - var buf bytes.Buffer - subD := *dec // capture the .curPos *before* calling .decodeString - dec.decodeString(&buf) - subD.r = &buf - subD.decode(fVal, false) - default: - dec.panicSyntax(fmt.Errorf(",string field: expected %q or %q but got %q", - 'n', '"', dec.peekRune())) - } - } else { - dec.decode(fVal, true) - } - }) - case reflect.Map: - switch dec.peekRune() { - case 'n': - dec.decodeNull() - val.Set(reflect.Zero(typ)) - case '{': - if val.IsNil() { - val.Set(reflect.MakeMap(typ)) - } - var nameBuf bytes.Buffer - dec.decodeObject(&nameBuf, func() { - nameValTyp := typ.Key() - nameValPtr := reflect.New(nameValTyp) - switch { - case reflect.PointerTo(nameValTyp).Implements(textUnmarshalerType): - obj := nameValPtr.Interface().(encoding.TextUnmarshaler) - if err := obj.UnmarshalText(nameBuf.Bytes()); err != nil { - dec.panicSyntax(err) - } - default: - switch nameValTyp.Kind() { - case reflect.String: - nameValPtr.Elem().SetString(nameBuf.String()) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - n, err := strconv.ParseInt(nameBuf.String(), 10, kind2bits[nameValTyp.Kind()]) - if err != nil { - dec.panicSyntax(err) - } - nameValPtr.Elem().SetInt(n) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - n, err := strconv.ParseUint(nameBuf.String(), 10, kind2bits[nameValTyp.Kind()]) - if err != nil { - dec.panicSyntax(err) - } - nameValPtr.Elem().SetUint(n) - default: - dec.panicType(typ, fmt.Errorf("invalid map key type: %v", nameValTyp)) - } - } - dec.stackPush(nameValPtr.Elem()) - defer dec.stackPop() - - fValPtr := reflect.New(typ.Elem()) - dec.decode(fValPtr.Elem(), false) - - val.SetMapIndex(nameValPtr.Elem(), fValPtr.Elem()) - }) - default: - dec.panicSyntax(fmt.Errorf("map: expected %q or %q bug got %q", 'n', '{', dec.peekRune())) - } - case reflect.Slice: - switch { - case typ.Elem().Kind() == reflect.Uint8: - switch dec.peekRune() { - case 'n': - dec.decodeNull() - val.Set(reflect.Zero(typ)) - case '"': - var buf bytes.Buffer - dec.decodeString(newBase64Decoder(&buf)) - if typ.Elem() == byteType { - val.Set(reflect.ValueOf(buf.Bytes())) - } else { - bs := buf.Bytes() - // TODO: Surely there's a better way. - val.Set(reflect.MakeSlice(typ, len(bs), len(bs))) - for i := 0; i < len(bs); i++ { - val.Index(i).Set(reflect.ValueOf(bs[i]).Convert(typ.Elem())) - } - } - default: - dec.panicSyntax(fmt.Errorf("byte slice: expected %q or %q but got %q", 'n', '"', dec.peekRune())) - } - default: - switch dec.peekRune() { - case 'n': - dec.decodeNull() - val.Set(reflect.Zero(typ)) - case '[': - if val.IsNil() { - val.Set(reflect.MakeSlice(typ, 0, 0)) - } - if val.Len() > 0 { - val.Set(val.Slice(0, 0)) - } - i := 0 - dec.decodeArray(func() { - dec.stackPush(i) - defer dec.stackPop() - mValPtr := reflect.New(typ.Elem()) - dec.decode(mValPtr.Elem(), false) - val.Set(reflect.Append(val, mValPtr.Elem())) - i++ - }) - default: - dec.panicSyntax(fmt.Errorf("slice: expected %q or %q but got %q", 'n', '[', dec.peekRune())) - } - } - case reflect.Array: - if nullOK && dec.peekRune() == 'n' { - dec.decodeNull() - return - } - i := 0 - n := val.Len() - dec.decodeArray(func() { - dec.stackPush(i) - defer dec.stackPop() - if i < n { - mValPtr := reflect.New(typ.Elem()) - dec.decode(mValPtr.Elem(), false) - val.Index(i).Set(mValPtr.Elem()) - } else { - dec.scan(io.Discard) - } - i++ - }) - for ; i < n; i++ { - val.Index(i).Set(reflect.Zero(typ.Elem())) - } - case reflect.Pointer: - switch dec.peekRune() { - case 'n': - dec.decodeNull() - /* - for typ.Elem().Kind() == reflect.Pointer { - if val.IsNil() || !val.Elem().CanSet() { - val.Set(reflect.New(typ.Elem())) - } - val = val.Elem() - typ = val.Type() - } - */ - val.Set(reflect.Zero(typ)) - default: - if val.IsNil() { - val.Set(reflect.New(typ.Elem())) - } - dec.decode(val.Elem(), false) - } - default: - dec.panicType(typ, fmt.Errorf("unsupported type (kind=%v)", typ.Kind())) - } - } -} - -func (dec *Decoder) decodeWS() { - for { - c, ok := dec.readRuneOrEOF() - if !ok { - return - } - switch c { - // NB: The JSON definition of whitespace is more - // narrow than unicode.IsSpace - case 0x0020, 0x000A, 0x000D, 0x0009: - // do nothing - default: - dec.unreadRune() - return - } - } -} - -func (dec *Decoder) scan(out io.Writer) { - scanner := &ReEncoder{ - Out: out, - Compact: true, - } - if _, err := scanner.WriteRune(dec.readRune()); err != nil { - dec.panicSyntax(err) - } - scanner.bailAfterCurrent = true - var err error - var eof bool - for err == nil { - c, ok := dec.readRuneOrEOF() - if ok { - _, err = scanner.WriteRune(c) - } else { - eof = true - err = scanner.Flush() - break - } - } - if err != nil { - if err == errBailedAfterCurrent { - if !eof { - dec.unreadRune() - } - } else { - dec.panicSyntax(err) - } - } -} - -func (dec *Decoder) scanNumber(out io.Writer) { - c := dec.peekRune() - switch c { - case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - dec.scan(out) - default: - dec.panicSyntax(fmt.Errorf("number: expected %q or a digit, but got %q", '-', c)) - } -} - -func (dec *Decoder) decodeAny() any { - c := dec.peekRune() - switch c { - case '{': - ret := make(map[string]any) - var nameBuf strings.Builder - dec.decodeObject(&nameBuf, func() { - name := nameBuf.String() - dec.stackPush(name) - defer dec.stackPop() - ret[name] = dec.decodeAny() - }) - return ret - case '[': - ret := []any{} - dec.decodeArray(func() { - dec.stackPush(len(ret)) - defer dec.stackPop() - ret = append(ret, dec.decodeAny()) - }) - return ret - case '"': - var buf strings.Builder - dec.decodeString(&buf) - return buf.String() - case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - var buf strings.Builder - dec.scanNumber(&buf) - num := json.Number(buf.String()) - if dec.useNumber { - return num - } - f64, err := num.Float64() - if err != nil { - dec.panicSyntax(err) - } - return f64 - case 't', 'f': - return dec.decodeBool() - case 'n': - dec.decodeNull() - return nil - default: - dec.panicSyntax(fmt.Errorf("any: unexpected character: %c", c)) - panic("not reached") - } -} - -func (dec *Decoder) decodeObject(nameBuf runeBuffer, decodeKVal func()) { - dec.expectRune('{') - dec.decodeWS() - c := dec.readRune() - switch c { - case '"': - decodeMember: - dec.unreadRune() - nameBuf.Reset() - dec.decodeString(nameBuf) - dec.decodeWS() - dec.expectRune(':') - dec.decodeWS() - decodeKVal() - dec.decodeWS() - c := dec.readRune() - switch c { - case ',': - dec.decodeWS() - dec.expectRune('"') - goto decodeMember - case '}': - return - default: - dec.panicSyntax(fmt.Errorf("object: expected %q or %q but got %q", ',', '}', c)) - } - case '}': - return - default: - dec.panicSyntax(fmt.Errorf("object: expected %q or %q but got %q", '"', '}', c)) - } -} - -func (dec *Decoder) decodeArray(decodeMember func()) { - dec.expectRune('[') - dec.decodeWS() - c := dec.readRune() - switch c { - case ']': - return - default: - dec.unreadRune() - decodeNextMember: - decodeMember() - dec.decodeWS() - c := dec.readRune() - switch c { - case ',': - dec.decodeWS() - goto decodeNextMember - case ']': - return - default: - dec.panicSyntax(fmt.Errorf("array: expected %c or %c but got %c", ',', ']', c)) - } - } -} - -func (dec *Decoder) decodeHex() rune { - c := dec.readRune() - switch { - case '0' <= c && c <= '9': - return c - '0' - case 'a' <= c && c <= 'f': - return c - 'a' + 10 - case 'A' <= c && c <= 'F': - return c - 'A' + 10 - default: - dec.panicSyntax(fmt.Errorf("string: expected a hex digit but got %q", c)) - panic("not reached") - } -} - -func (dec *Decoder) decodeString(out io.Writer) { - dec.expectRune('"') - for { - c := dec.readRune() - switch { - case 0x0020 <= c && c <= 0x10FFFF && c != '"' && c != '\\': - if _, err := writeRune(out, c); err != nil { - dec.panicSyntax(err) - } - case c == '\\': - c = dec.readRune() - switch c { - case '"': - if _, err := writeRune(out, '"'); err != nil { - dec.panicSyntax(err) - } - case '\\': - if _, err := writeRune(out, '\\'); err != nil { - dec.panicSyntax(err) - } - case '/': - if _, err := writeRune(out, '/'); err != nil { - dec.panicSyntax(err) - } - case 'b': - if _, err := writeRune(out, '\b'); err != nil { - dec.panicSyntax(err) - } - case 'f': - if _, err := writeRune(out, '\f'); err != nil { - dec.panicSyntax(err) - } - case 'n': - if _, err := writeRune(out, '\n'); err != nil { - dec.panicSyntax(err) - } - case 'r': - if _, err := writeRune(out, '\r'); err != nil { - dec.panicSyntax(err) - } - case 't': - if _, err := writeRune(out, '\t'); err != nil { - dec.panicSyntax(err) - } - case 'u': - c = dec.decodeHex() - c = (c << 4) | dec.decodeHex() - c = (c << 4) | dec.decodeHex() - c = (c << 4) | dec.decodeHex() - if _, err := writeRune(out, c); err != nil { - dec.panicSyntax(err) - } - } - case c == '"': - return - default: - dec.panicSyntax(fmt.Errorf("string: unexpected %c", c)) - } - } -} - -func (dec *Decoder) decodeBool() bool { - c := dec.readRune() - switch c { - case 't': - dec.expectRune('r') - dec.expectRune('u') - dec.expectRune('e') - return true - case 'f': - dec.expectRune('a') - dec.expectRune('l') - dec.expectRune('s') - dec.expectRune('e') - return false - default: - dec.panicSyntax(fmt.Errorf("bool: expected %q or %q but got %q", 't', 'f', c)) - panic("not reached") - } -} - -func (dec *Decoder) decodeNull() { - dec.expectRune('n') - dec.expectRune('u') - dec.expectRune('l') - dec.expectRune('l') -} diff --git a/lib/lowmemjson/encode.go b/lib/lowmemjson/encode.go deleted file mode 100644 index 377b9b9..0000000 --- a/lib/lowmemjson/encode.go +++ /dev/null @@ -1,333 +0,0 @@ -// Copyright (C) 2022 Luke Shumaker -// -// SPDX-License-Identifier: GPL-2.0-or-later - -package lowmemjson - -import ( - "bytes" - "encoding" - "encoding/base64" - "encoding/json" - "io" - "reflect" - "sort" - "strconv" - "strings" -) - -type Encodable interface { - EncodeJSON(w io.Writer) error -} - -type encodeError struct { - Err error -} - -func encodeWriteByte(w io.Writer, b byte) { - if err := writeByte(w, b); err != nil { - panic(encodeError{err}) - } -} - -func encodeWriteString(w io.Writer, str string) { - if _, err := io.WriteString(w, str); err != nil { - panic(encodeError{err}) - } -} - -func Encode(w io.Writer, obj any) (err error) { - defer func() { - if r := recover(); r != nil { - if e, ok := r.(encodeError); ok { - err = e.Err - } else { - panic(r) - } - } - }() - encode(w, reflect.ValueOf(obj), false) - if f, ok := w.(interface{ Flush() error }); ok { - return f.Flush() - } - return nil -} - -var ( - encodableType = reflect.TypeOf((*Encodable)(nil)).Elem() - jsonMarshalerType = reflect.TypeOf((*json.Marshaler)(nil)).Elem() - textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem() -) - -func encode(w io.Writer, val reflect.Value, quote bool) { - if !val.IsValid() { - encodeWriteString(w, "null") - return - } - switch { - - case val.Kind() != reflect.Pointer && val.CanAddr() && reflect.PointerTo(val.Type()).Implements(encodableType): - val = val.Addr() - fallthrough - case val.Type().Implements(encodableType): - if val.Kind() == reflect.Pointer && val.IsNil() { - encodeWriteString(w, "null") - return - } - obj, ok := val.Interface().(Encodable) - if !ok { - encodeWriteString(w, "null") - return - } - if err := obj.EncodeJSON(w); err != nil { - panic(encodeError{err}) - } - - case val.Kind() != reflect.Pointer && val.CanAddr() && reflect.PointerTo(val.Type()).Implements(jsonMarshalerType): - val = val.Addr() - fallthrough - case val.Type().Implements(jsonMarshalerType): - if val.Kind() == reflect.Pointer && val.IsNil() { - encodeWriteString(w, "null") - return - } - obj, ok := val.Interface().(json.Marshaler) - if !ok { - encodeWriteString(w, "null") - return - } - dat, err := obj.MarshalJSON() - if err != nil { - panic(encodeError{err}) - } - if _, err := w.Write(dat); err != nil { - panic(encodeError{err}) - } - - case val.Kind() != reflect.Pointer && val.CanAddr() && reflect.PointerTo(val.Type()).Implements(textMarshalerType): - val = val.Addr() - fallthrough - case val.Type().Implements(textMarshalerType): - if val.Kind() == reflect.Pointer && val.IsNil() { - encodeWriteString(w, "null") - return - } - obj, ok := val.Interface().(encoding.TextMarshaler) - if !ok { - encodeWriteString(w, "null") - return - } - text, err := obj.MarshalText() - if err != nil { - panic(encodeError{err}) - } - encodeString(w, text) - - default: - switch val.Kind() { - case reflect.Bool: - if quote { - encodeWriteByte(w, '"') - } - if val.Bool() { - encodeWriteString(w, "true") - } else { - encodeWriteString(w, "false") - } - if quote { - encodeWriteByte(w, '"') - } - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - if quote { - encodeWriteByte(w, '"') - } - encodeWriteString(w, strconv.FormatInt(val.Int(), 10)) - if quote { - encodeWriteByte(w, '"') - } - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - if quote { - encodeWriteByte(w, '"') - } - encodeWriteString(w, strconv.FormatUint(val.Uint(), 10)) - if quote { - encodeWriteByte(w, '"') - } - case reflect.Float32, reflect.Float64: - if quote { - encodeWriteByte(w, '"') - } - encodeTODO(w, val) - if quote { - encodeWriteByte(w, '"') - } - case reflect.String: - if val.Type() == numberType { - numStr := val.String() - if numStr == "" { - numStr = "0" - } - if quote { - encodeWriteByte(w, '"') - } - encodeWriteString(w, numStr) - if quote { - encodeWriteByte(w, '"') - } - } else { - if quote { - var buf bytes.Buffer - encodeString(&buf, val.String()) - encodeString(w, buf.Bytes()) - } else { - encodeString(w, val.String()) - } - } - case reflect.Interface: - if val.IsNil() { - encodeWriteString(w, "null") - } else { - encode(w, val.Elem(), quote) - } - case reflect.Struct: - encodeWriteByte(w, '{') - empty := true - for _, field := range indexStruct(val.Type()).byPos { - fVal, err := val.FieldByIndexErr(field.Path) - if err != nil { - continue - } - if field.OmitEmpty && isEmptyValue(fVal) { - continue - } - if !empty { - encodeWriteByte(w, ',') - } - empty = false - encodeString(w, field.Name) - encodeWriteByte(w, ':') - encode(w, fVal, field.Quote) - } - encodeWriteByte(w, '}') - case reflect.Map: - if val.IsNil() { - encodeWriteString(w, "null") - return - } - if val.Len() == 0 { - encodeWriteString(w, "{}") - return - } - encodeWriteByte(w, '{') - - type kv struct { - K string - V reflect.Value - } - kvs := make([]kv, val.Len()) - iter := val.MapRange() - for i := 0; iter.Next(); i++ { - var k strings.Builder - encode(&k, iter.Key(), false) - kStr := k.String() - if kStr == "null" { - kStr = `""` - } - if !strings.HasPrefix(kStr, `"`) { - k.Reset() - encodeString(&k, kStr) - kStr = k.String() - } - kvs[i].K = kStr - kvs[i].V = iter.Value() - } - sort.Slice(kvs, func(i, j int) bool { - return kvs[i].K < kvs[j].K - }) - - for i, kv := range kvs { - if i > 0 { - encodeWriteByte(w, ',') - } - encodeWriteString(w, kv.K) - encodeWriteByte(w, ':') - encode(w, kv.V, false) - } - encodeWriteByte(w, '}') - case reflect.Slice: - switch { - case val.IsNil(): - encodeWriteString(w, "null") - case val.Type().Elem().Kind() == reflect.Uint8: - encodeWriteByte(w, '"') - enc := base64.NewEncoder(base64.StdEncoding, w) - if val.CanConvert(byteSliceType) { - if _, err := enc.Write(val.Convert(byteSliceType).Interface().([]byte)); err != nil { - panic(encodeError{err}) - } - } else { - // TODO: Surely there's a better way. - for i, n := 0, val.Len(); i < n; i++ { - var buf [1]byte - buf[0] = val.Index(i).Convert(byteType).Interface().(byte) - if _, err := enc.Write(buf[:]); err != nil { - panic(encodeError{err}) - } - } - } - if err := enc.Close(); err != nil { - panic(encodeError{err}) - } - encodeWriteByte(w, '"') - default: - encodeArray(w, val) - } - case reflect.Array: - encodeArray(w, val) - case reflect.Pointer: - if val.IsNil() { - encodeWriteString(w, "null") - } else { - encode(w, val.Elem(), quote) - } - default: - panic(encodeError{&json.UnsupportedTypeError{ - Type: val.Type(), - }}) - } - } -} - -func encodeString[T interface{ []byte | string }](w io.Writer, str T) { - encodeWriteByte(w, '"') - for i := 0; i < len(str); { - c, size := decodeRune(str[i:]) - if _, err := writeStringChar(w, c, false, nil); err != nil { - panic(encodeError{err}) - } - i += size - } - encodeWriteByte(w, '"') -} - -func encodeArray(w io.Writer, val reflect.Value) { - encodeWriteByte(w, '[') - n := val.Len() - for i := 0; i < n; i++ { - if i > 0 { - encodeWriteByte(w, ',') - } - encode(w, val.Index(i), false) - } - encodeWriteByte(w, ']') -} - -func encodeTODO(w io.Writer, val reflect.Value) { - bs, err := json.Marshal(val.Interface()) - if err != nil { - panic(encodeError{err}) - } - if _, err := w.Write(bs); err != nil { - panic(encodeError{err}) - } -} diff --git a/lib/lowmemjson/misc.go b/lib/lowmemjson/misc.go deleted file mode 100644 index 132b177..0000000 --- a/lib/lowmemjson/misc.go +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (C) 2022 Luke Shumaker -// -// SPDX-License-Identifier: GPL-2.0-or-later - -package lowmemjson - -import ( - "encoding/json" - "io" - "reflect" - "unicode/utf8" -) - -const Tab = "\t" - -const hex = "0123456789abcdef" - -var ( - numberType = reflect.TypeOf(json.Number("")) - byteType = reflect.TypeOf(byte(0)) - byteSliceType = reflect.TypeOf(([]byte)(nil)) -) - -// generic I/O ///////////////////////////////////////////////////////////////// - -func decodeRune[T interface{ []byte | string }](s T) (r rune, size int) { - iface := any(s) - if str, ok := iface.(string); ok { - return utf8.DecodeRuneInString(str) - } else { - return utf8.DecodeRune(iface.([]byte)) - } -} - -func writeByte(w io.Writer, c byte) error { - if br, ok := w.(interface{ WriteByte(byte) error }); ok { - return br.WriteByte(c) - } - var buf [1]byte - buf[0] = c - if _, err := w.Write(buf[:]); err != nil { - return err - } - return nil -} - -func writeRune(w io.Writer, c rune) (int, error) { - if rw, ok := w.(interface{ WriteRune(rune) (int, error) }); ok { - return rw.WriteRune(c) - } - var buf [utf8.UTFMax]byte - n := utf8.EncodeRune(buf[:], c) - return w.Write(buf[:n]) -} - -// JSON string encoding //////////////////////////////////////////////////////// - -func UnicodeEscapeJSSafe(c rune, _ bool) bool { - // JSON is notionally a JS subset, but that's not actually - // true. - // - // http://timelessrepo.com/json-isnt-a-javascript-subset - switch c { - case '\u2028', '\u2029': - return true - default: - return false - } -} - -func UnicodeEscapeHTMLSafe(c rune, wasEscaped bool) bool { - switch c { - case '&', '<', '>': - return true - default: - return UnicodeEscapeJSSafe(c, wasEscaped) - } -} - -func UnicodeEscapeDefault(c rune, wasEscaped bool) bool { - switch c { - case '\b', '\f', utf8.RuneError: - return true - default: - return UnicodeEscapeHTMLSafe(c, wasEscaped) - } -} - -func writeStringUnicodeEscape(w io.Writer, c rune) (int, error) { - buf := [6]byte{ - '\\', - 'u', - hex[(c>>12)&0xf], - hex[(c>>8)&0xf], - hex[(c>>4)&0xf], - hex[(c>>0)&0xf], - } - return w.Write(buf[:]) -} -func writeStringShortEscape(w io.Writer, c byte) (int, error) { - buf := [2]byte{'\\', c} - return w.Write(buf[:]) -} -func writeStringChar(w io.Writer, c rune, wasEscaped bool, escaper func(rune, bool) bool) (int, error) { - if escaper == nil { - escaper = UnicodeEscapeDefault - } - switch { - case c <= 0xFFFF && escaper(c, wasEscaped): - return writeStringUnicodeEscape(w, c) - case c == '"' || c == '\\': - return writeStringShortEscape(w, byte(c)) - case c < 0x0020: - switch c { - case '\b': - return writeStringShortEscape(w, 'b') - case '\f': - return writeStringShortEscape(w, 'f') - case '\n': - return writeStringShortEscape(w, 'n') - case '\r': - return writeStringShortEscape(w, 'r') - case '\t': - return writeStringShortEscape(w, 't') - default: - return writeStringUnicodeEscape(w, c) - } - default: - return writeRune(w, c) - } -} diff --git a/lib/lowmemjson/reencode.go b/lib/lowmemjson/reencode.go deleted file mode 100644 index 50c8ba3..0000000 --- a/lib/lowmemjson/reencode.go +++ /dev/null @@ -1,598 +0,0 @@ -// Copyright (C) 2022 Luke Shumaker -// -// SPDX-License-Identifier: GPL-2.0-or-later - -package lowmemjson - -import ( - "errors" - "fmt" - "io" - "unicode/utf8" -) - -type reencodeState func(rune) error - -type ReEncoder struct { - Out io.Writer - - // Whether to minify the JSON. - Compact bool - // String to use to indent; ignored if Compact is true. - Indent string - // String to put before indents, for testing-compat with - // encoding/json only. - prefix string - // Returns whether a given character in a string should be - // "\uXXXX" escaped. The bool argument is whether it was - // \u-escaped in the input. This does not affect characters - // that must or must-not be \u-escaped to be valid JSON. - // - // If not set, then EscapeUnicodeDefault is used. - UnicodeEscape func(rune, bool) bool - - bailAfterCurrent bool - - // state: .Write's utf8-decoding buffer - buf [utf8.UTFMax]byte - bufLen int - - // state: .WriteRune - err error - inputPos int64 - written int - stack []reencodeState - stack0IsNumber bool - curIndent int - - // state: reencodeState-specific - stateBuf []byte -} - -// public API ////////////////////////////////////////////////////////////////// - -func (enc *ReEncoder) Write(p []byte) (int, error) { - if len(p) == 0 { - return 0, nil - } - var n int - if enc.bufLen > 0 { - copy(enc.buf[enc.bufLen:], p) - c, size := utf8.DecodeRune(enc.buf[:]) - n += size - enc.bufLen - enc.bufLen = 0 - if _, err := enc.WriteRune(c); err != nil { - return 0, err - } - } - for utf8.FullRune(p[n:]) { - c, size := utf8.DecodeRune(p[n:]) - if _, err := enc.WriteRune(c); err != nil { - return n, err - } - n += size - } - enc.bufLen = copy(enc.buf[:], p[n:]) - return len(p), nil -} - -func (enc *ReEncoder) Flush() error { - if enc.bufLen > 0 { - return &SyntaxError{fmt.Sprintf("EOF: unflushed unicode garbage: %q", enc.buf[:enc.bufLen]), enc.inputPos} - } - switch len(enc.stack) { - case 0: - return nil - case 1: - if enc.stack0IsNumber { - enc.Compact = true - return enc.state('\n') - } - fallthrough - default: - return &SyntaxError{fmt.Sprintf("EOF: in the middle of a value"), enc.inputPos} - } -} - -func (enc *ReEncoder) WriteRune(c rune) (n int, err error) { - if enc.err != nil { - return 0, enc.err - } - if enc.bufLen != 0 { - enc.err = errors.New("lowmemjson.ReEncoder: cannot .WriteRune() when there is a partial rune that has been .Write()n") - return 0, enc.err - } - enc.written = 0 - enc.err = enc.state(c) - enc.inputPos += int64(utf8.RuneLen(c)) - return enc.written, enc.err -} - -// io helpers ////////////////////////////////////////////////////////////////// - -func (enc *ReEncoder) emitByte(c byte) error { - err := writeByte(enc.Out, c) - if err == nil { - enc.written++ - } - return err -} - -func (enc *ReEncoder) emit(n int, err error) error { - enc.written += n - return err -} - -func (enc *ReEncoder) nlIndent() error { - if enc.Compact || enc.Indent == "" { - return nil - } - if err := enc.emitByte('\n'); err != nil { - return err - } - if enc.prefix != "" { - if err := enc.emit(io.WriteString(enc.Out, enc.prefix)); err != nil { - return err - } - } - for i := 0; i < enc.curIndent; i++ { - if err := enc.emit(io.WriteString(enc.Out, enc.Indent)); err != nil { - return err - } - } - return nil -} - -// state helpers /////////////////////////////////////////////////////////////// - -func (enc *ReEncoder) pushState(state reencodeState, isNumber bool) { - if len(enc.stack) == 0 { - enc.stack0IsNumber = isNumber - } - enc.stack = append(enc.stack, state) -} -func (enc *ReEncoder) replaceState(state reencodeState, isNumber bool) { - if len(enc.stack) == 1 { - enc.stack0IsNumber = isNumber - } - enc.stack[len(enc.stack)-1] = state -} -func (enc *ReEncoder) popState() { - if len(enc.stack) == 1 { - enc.stack0IsNumber = false - } - enc.stack = enc.stack[:len(enc.stack)-1] -} - -var errBailedAfterCurrent = errors.New("bailed after current") - -func (enc *ReEncoder) state(c rune) error { - if len(enc.stack) == 0 { - if enc.bailAfterCurrent { - return errBailedAfterCurrent - } - enc.pushState(enc.stateAny, false) - } - return enc.stack[len(enc.stack)-1](c) -} - -// any ///////////////////////////////////////////////////////////////////////////////////////////// - -func (enc *ReEncoder) stateAny(c rune) error { - switch c { - case 0x0020, 0x000A, 0x000D, 0x0009: - if enc.Compact || enc.Indent != "" { - return nil - } - case '{': - enc.replaceState(enc.stateInEmptyObject, false) - enc.curIndent++ - case '[': - enc.replaceState(enc.stateInEmptyArray, false) - enc.curIndent++ - case '"': - enc.replaceState(enc.stateInString, false) - case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - enc.replaceState(enc.stateNumberA, true) - return enc.state(c) - case 't': - enc.replaceState(enc.stateInTrue, false) - enc.stateBuf = append(enc.stateBuf[:0], 't') - case 'f': - enc.replaceState(enc.stateInFalse, false) - enc.stateBuf = append(enc.stateBuf[:0], 'f') - case 'n': - enc.replaceState(enc.stateInNull, false) - enc.stateBuf = append(enc.stateBuf[:0], 'n') - default: - return &SyntaxError{fmt.Sprintf("any: unexpected character: %c", c), enc.inputPos} - } - return enc.emitByte(byte(c)) -} - -// object ////////////////////////////////////////////////////////////////////////////////////////// - -func (enc *ReEncoder) stateInEmptyObject(c rune) error { return enc._stateInObject(c, false) } -func (enc *ReEncoder) stateInNonEmptyObject(c rune) error { return enc._stateInObject(c, true) } -func (enc *ReEncoder) _stateInObject(c rune, nonempty bool) error { - switch c { - case 0x0020, 0x000A, 0x000D, 0x0009: - if enc.Compact || enc.Indent != "" { - return nil - } - case '"': - if err := enc.nlIndent(); err != nil { - return err - } - enc.replaceState(enc.stateInKV, false) - enc.pushState(enc.stateInString, false) - case '}': - enc.popState() - enc.curIndent-- - if nonempty { - if err := enc.nlIndent(); err != nil { - return err - } - } - default: - return &SyntaxError{fmt.Sprintf("object: unexpected character: %c", c), enc.inputPos} - } - return enc.emitByte(byte(c)) -} -func (enc *ReEncoder) stateInKV(c rune) error { - switch c { - case 0x0020, 0x000A, 0x000D, 0x0009: - if enc.Compact || enc.Indent != "" { - return nil - } - return enc.emitByte(byte(c)) - case ':': - enc.replaceState(enc.stateAfterV, false) - enc.pushState(enc.stateAny, false) - if err := enc.emitByte(byte(c)); err != nil { - return err - } - if !enc.Compact && enc.Indent != "" { - return enc.emitByte(' ') - } - return nil - default: - return &SyntaxError{fmt.Sprintf("object member: unexpected character: %c", c), enc.inputPos} - } -} -func (enc *ReEncoder) stateAfterV(c rune) error { - switch c { - case 0x0020, 0x000A, 0x000D, 0x0009: - if enc.Compact || enc.Indent != "" { - return nil - } - case ',': - enc.replaceState(enc.stateInNonEmptyObject, false) - case '}': - enc.popState() - enc.curIndent-- - if err := enc.nlIndent(); err != nil { - return err - } - default: - return &SyntaxError{fmt.Sprintf("object member: unexpected character: %c", c), enc.inputPos} - } - return enc.emitByte(byte(c)) -} - -// array /////////////////////////////////////////////////////////////////////////////////////////// - -func (enc *ReEncoder) stateInEmptyArray(c rune) error { return enc._stateInArray(c, false) } -func (enc *ReEncoder) stateInNonEmptyArray(c rune) error { return enc._stateInArray(c, true) } -func (enc *ReEncoder) _stateInArray(c rune, nonempty bool) error { - switch c { - case 0x0020, 0x000A, 0x000D, 0x0009: - if enc.Compact || enc.Indent != "" { - return nil - } - case ']': - enc.popState() - enc.curIndent-- - if nonempty { - if err := enc.nlIndent(); err != nil { - return err - } - } - default: - if err := enc.nlIndent(); err != nil { - return err - } - enc.replaceState(enc.stateAfterItem, false) - enc.pushState(enc.stateAny, false) - return enc.state(c) - } - return enc.emitByte(byte(c)) -} -func (enc *ReEncoder) stateAfterItem(c rune) error { - switch c { - case 0x0020, 0x000A, 0x000D, 0x0009: - if enc.Compact || enc.Indent != "" { - return nil - } - case ',': - enc.replaceState(enc.stateInNonEmptyArray, false) - case ']': - enc.popState() - enc.curIndent-- - if err := enc.nlIndent(); err != nil { - return err - } - default: - return &SyntaxError{fmt.Sprintf("array: unexpected character: %c", c), enc.inputPos} - } - return enc.emitByte(byte(c)) -} - -// string ////////////////////////////////////////////////////////////////////////////////////////// - -func (enc *ReEncoder) stateInString(c rune) error { - switch { - case c == '\\': - enc.replaceState(enc.stateInBackslash, false) - return nil - case c == '"': - enc.popState() - return enc.emitByte(byte(c)) - case 0x0020 <= c && c <= 0x10FFFF: - return enc.emit(writeStringChar(enc.Out, c, false, enc.UnicodeEscape)) - default: - return &SyntaxError{fmt.Sprintf("string: unexpected character: %c", c), enc.inputPos} - } -} -func (enc *ReEncoder) stateInBackslash(c rune) error { - switch c { - case '"': - enc.replaceState(enc.stateInString, false) - return enc.emit(writeStringChar(enc.Out, '"', false, enc.UnicodeEscape)) - case '\\': - enc.replaceState(enc.stateInString, false) - return enc.emit(writeStringChar(enc.Out, '\\', false, enc.UnicodeEscape)) - case '/': - enc.replaceState(enc.stateInString, false) - return enc.emit(writeStringChar(enc.Out, '/', false, enc.UnicodeEscape)) - case 'b': - enc.replaceState(enc.stateInString, false) - return enc.emit(writeStringChar(enc.Out, '\b', false, enc.UnicodeEscape)) - case 'f': - enc.replaceState(enc.stateInString, false) - return enc.emit(writeStringChar(enc.Out, '\f', false, enc.UnicodeEscape)) - case 'n': - enc.replaceState(enc.stateInString, false) - return enc.emit(writeStringChar(enc.Out, '\n', false, enc.UnicodeEscape)) - case 'r': - enc.replaceState(enc.stateInString, false) - return enc.emit(writeStringChar(enc.Out, '\r', false, enc.UnicodeEscape)) - case 't': - enc.replaceState(enc.stateInString, false) - return enc.emit(writeStringChar(enc.Out, '\t', false, enc.UnicodeEscape)) - case 'u': - enc.replaceState(enc.stateInUnicode, false) - return nil - default: - return &SyntaxError{fmt.Sprintf("string backslash sequence: unexpected character: %c", c), enc.inputPos} - } -} -func (enc *ReEncoder) stateInUnicode(c rune) error { - switch { - case '0' <= c && c <= '9': - enc.stateBuf = append(enc.stateBuf, byte(c)-'0') - case 'a' <= c && c <= 'f': - enc.stateBuf = append(enc.stateBuf, byte(c)-'a'+10) - case 'A' <= c && c <= 'F': - enc.stateBuf = append(enc.stateBuf, byte(c)-'A'+10) - default: - return &SyntaxError{fmt.Sprintf("string unicode sequence: unexpected character: %c", c), enc.inputPos} - } - if len(enc.stateBuf) == 4 { - enc.replaceState(enc.stateInString, false) - c := 0 | - rune(enc.stateBuf[0])<<12 | - rune(enc.stateBuf[1])<<8 | - rune(enc.stateBuf[2])<<4 | - rune(enc.stateBuf[3])<<0 - enc.stateBuf = enc.stateBuf[:0] - return enc.emit(writeStringChar(enc.Out, c, true, enc.UnicodeEscape)) - } - return nil -} - -// number ////////////////////////////////////////////////////////////////////////////////////////// - -// Here's a flattened drawing of the syntax diagram from www.json.org : -// -// [------------ integer ----------][-- fraction ---][-------- exponent -------] -// >─╮─────╭─╮─"0"───────╭─────────╭──╮─────────────╭──╮───────────────────────╭─> -// │ │ │ │ │ │ │ │ │ -// ╰─"-"─╯ ╰─digit 1-9─╯─╭digit╮─╯ ╰─"."─╭digit╮─╯ ╰─"e"─╭─╮─────╭─╭digit╮─╯ -// ╰──<──╯ ╰──<──╯ │ │ │ │ ╰──<──╯ -// ╰─"E"─╯ ╰─"-"─╯ -// │ │ -// ╰─"+"─╯ -// -// Now here it is slightly redrawn, and with each distinct state our -// decoder can be in marked with a single-capital-letter: -// -// [-------------- integer ------------][--------- fraction --------][--------- exponent ---------] -// >─A─╮───────╭──╮─"0"─────────C─╭─────────╮──────────────────╭─────────╮──────────────────────────╭─> -// │ │ │ │ │ │ │ │ -// ╰─"-"─B─╯ ╰─digit 1-9─╭─D─╯─digit╮ ╰─"."─E─digit──╭─F─╯─digit╮ ╰─"e"─╭─G─╮─────╭─╭digit─H─╯ -// ╰────<─────╯ ╰────<─────╯ │ │ │ │ ╰────<───╯ -// ╰─"E"─╯ ╰─"-"─╯ -// │ │ -// ╰─"+"─╯ -// -// Which state we're at is the 'X' in 'stateNumberX'. -// -// Besides just traversing that, there are a few compressions we want to make: -// -// - trim trailing 0s from fraction the (but don't remove the -// fraction if it's all 0s); do this by making the F state a little -// special. This requires a little more state, because when we -// encounter the 0 we don't yet know if it's trailing. So, store -// the number of maybe-trailing zeros in enc.stateBuf[0]; if that -// reaches 255, then bleed over to enc.stateBuf[1] and so on. -// -// - trim leading 0s from the exponent (but don't remove the exponent -// if it's all 0s); do this by making the H state a little special. -// Record whether we've seen a non-zero digit in enc.stateBuf[0] -// (0=false, 1=true). - -// integer-part //////////////////////////////////////////////////////////////// -func (enc *ReEncoder) stateNumberA(c rune) error { // start - switch c { - case '-': - enc.replaceState(enc.stateNumberB, true) - case '0': - enc.replaceState(enc.stateNumberC, true) - case '1', '2', '3', '4', '5', '6', '7', '8', '9': - enc.replaceState(enc.stateNumberD, true) - default: - return &SyntaxError{fmt.Sprintf("number: unexpected character: %c", c), enc.inputPos} - } - return enc.emitByte(byte(c)) -} -func (enc *ReEncoder) stateNumberB(c rune) error { // got a leading "-" - switch c { - case '0': - enc.replaceState(enc.stateNumberC, true) - case '1', '2', '3', '4', '5', '6', '7', '8', '9': - enc.replaceState(enc.stateNumberD, true) - default: - return &SyntaxError{fmt.Sprintf("number: unexpected character: %c", c), enc.inputPos} - } - return enc.emitByte(byte(c)) -} -func (enc *ReEncoder) stateNumberC(c rune) error { // ready for the fraction or exponent part to start - switch c { - case '.': - enc.replaceState(enc.stateNumberE, true) - return enc.emitByte('.') - case 'e', 'E': - enc.replaceState(enc.stateNumberG, true) - enc.stateBuf = append(enc.stateBuf[:0], 0) - return enc.emitByte('e') - default: - enc.popState() - return enc.state(c) - } -} -func (enc *ReEncoder) stateNumberD(c rune) error { // in the integer part - switch c { - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - return enc.emitByte(byte(c)) - case '.': - enc.replaceState(enc.stateNumberE, true) - return enc.emitByte('.') - case 'e', 'E': - enc.replaceState(enc.stateNumberG, true) - enc.stateBuf = append(enc.stateBuf[:0], 0) - return enc.emitByte('e') - default: - enc.popState() - return enc.state(c) - } -} - -// fraction-part /////////////////////////////////////////////////////////////// -func (enc *ReEncoder) stateNumberE(c rune) error { // got a ".", ready to read a number for the fraction part - switch c { - case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - enc.replaceState(enc.stateNumberF, true) - return enc.emitByte(byte(c)) - default: - return &SyntaxError{fmt.Sprintf("number: unexpected character: %c", c), enc.inputPos} - } -} -func (enc *ReEncoder) stateNumberF(c rune) error { // in the fraction part - switch c { - case '0': - if len(enc.stateBuf) > 0 && enc.stateBuf[len(enc.stateBuf)-1] < 255 { - enc.stateBuf[len(enc.stateBuf)-1]++ - } else { - enc.stateBuf = append(enc.stateBuf, 1) - } - return nil - case '1', '2', '3', '4', '5', '6', '7', '8', '9': - for len(enc.stateBuf) > 0 { - if err := enc.emitByte('0'); err != nil { - return err - } - if enc.stateBuf[len(enc.stateBuf)-1] == 1 { - enc.stateBuf = enc.stateBuf[:len(enc.stateBuf)-1] - } else { - enc.stateBuf[len(enc.stateBuf)-1]-- - } - } - return enc.emitByte(byte(c)) - case 'e', 'E': - enc.replaceState(enc.stateNumberG, true) - enc.stateBuf = append(enc.stateBuf[:0], 0) - return enc.emitByte('e') - default: - enc.stateBuf = enc.stateBuf[:0] - enc.popState() - return enc.state(c) - } -} - -// exponent-part /////////////////////////////////////////////////////////////// -func (enc *ReEncoder) stateNumberG(c rune) error { // got a leading "e" - switch c { - case '-', '+': - enc.replaceState(enc.stateNumberH, true) - return enc.emitByte(byte(c)) - case '0': - enc.replaceState(enc.stateNumberH, true) - return nil - case '1', '2', '3', '4', '5', '6', '7', '8', '9': - enc.replaceState(enc.stateNumberH, true) - enc.stateBuf[0] = 1 - return enc.emitByte(byte(c)) - default: - enc.stateBuf = enc.stateBuf[:0] - return &SyntaxError{fmt.Sprintf("number: unexpected character: %c", c), enc.inputPos} - } -} -func (enc *ReEncoder) stateNumberH(c rune) error { // in the exponent's number part - switch c { - case '0': - if enc.stateBuf[0] == 0 { - return nil - } - return enc.emitByte('0') - case '1', '2', '3', '4', '5', '6', '7', '8', '9': - enc.stateBuf[0] = 1 - return enc.emitByte(byte(c)) - default: - if enc.stateBuf[0] == 0 { - if err := enc.emitByte('0'); err != nil { - return err - } - } - enc.stateBuf = enc.stateBuf[:0] - enc.popState() - return enc.state(c) - } -} - -// literals //////////////////////////////////////////////////////////////////////////////////////// - -func (enc *ReEncoder) stateInTrue(c rune) error { return enc._stateInLiteral(c, "true") } -func (enc *ReEncoder) stateInFalse(c rune) error { return enc._stateInLiteral(c, "false") } -func (enc *ReEncoder) stateInNull(c rune) error { return enc._stateInLiteral(c, "null") } -func (enc *ReEncoder) _stateInLiteral(c rune, full string) error { - if c != rune(full[len(enc.stateBuf)]) { - return &SyntaxError{fmt.Sprintf("%s: unexpected character: %c", full, c), enc.inputPos} - } - enc.stateBuf = append(enc.stateBuf, byte(c)) - if len(enc.stateBuf) == len(full) { - enc.stateBuf = enc.stateBuf[:0] - enc.popState() - } - return enc.emitByte(byte(c)) -} diff --git a/lib/lowmemjson/struct.go b/lib/lowmemjson/struct.go deleted file mode 100644 index ad142d6..0000000 --- a/lib/lowmemjson/struct.go +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright (C) 2022 Luke Shumaker -// -// SPDX-License-Identifier: GPL-2.0-or-later - -package lowmemjson - -import ( - "reflect" -) - -type structField struct { - Name string - Path []int - Tagged bool - OmitEmpty bool - Quote bool -} - -type structIndex struct { - byPos []structField - byName map[string]int -} - -func indexStruct(typ reflect.Type) structIndex { - byName := make(map[string][]structField) - var byPos []string - - indexStructInner(typ, nil, byName, &byPos) - - ret := structIndex{ - byName: make(map[string]int), - } - - for _, name := range byPos { - fields := byName[name] - delete(byName, name) - switch len(fields) { - case 0: - // do nothing - case 1: - ret.byName[name] = len(ret.byPos) - ret.byPos = append(ret.byPos, fields[0]) - default: - // To quote the encoding/json docs (version 1.18.4): - // - // If there are multiple fields at the same level, and that level is the - // least nested (and would therefore be the nesting level selected by the - // usual Go rules), the following extra rules apply: - // - // 1) Of those fields, if any are JSON-tagged, only tagged fields are - // considered, even if there are multiple untagged fields that would - // otherwise conflict. - // - // 2) If there is exactly one field (tagged or not according to the first - // rule), that is selected. - // - // 3) Otherwise there are multiple fields, and all are ignored; no error - // occurs. - leastLevel := len(fields[0].Path) - for _, field := range fields[1:] { - if len(field.Path) < leastLevel { - leastLevel = len(field.Path) - } - } - var numUntagged, numTagged int - var untaggedIdx, taggedIdx int - for i, field := range fields { - if len(field.Path) != leastLevel { - continue - } - if field.Tagged { - numTagged++ - taggedIdx = i - if numTagged > 1 { - break // optimization - } - } else { - numUntagged++ - untaggedIdx = i - } - } - switch numTagged { - case 0: - switch numUntagged { - case 0: - // do nothing - case 1: - ret.byName[name] = len(ret.byPos) - ret.byPos = append(ret.byPos, fields[untaggedIdx]) - } - case 1: - ret.byName[name] = len(ret.byPos) - ret.byPos = append(ret.byPos, fields[taggedIdx]) - } - } - } - - return ret -} - -func indexStructInner(typ reflect.Type, prefix []int, byName map[string][]structField, byPos *[]string) { - n := typ.NumField() - for i := 0; i < n; i++ { - path := append(append([]int(nil), prefix...), i) - - fTyp := typ.Field(i) - var embed bool - if fTyp.Anonymous { - t := fTyp.Type - if t.Kind() == reflect.Pointer { - t = t.Elem() - } - if !fTyp.IsExported() && t.Kind() != reflect.Struct { - continue - } - embed = t.Kind() == reflect.Struct - } else if !fTyp.IsExported() { - continue - } - tag := fTyp.Tag.Get("json") - if tag == "-" { - continue - } - tagName, opts := parseTag(tag) - name := tagName - if name == "" { - name = fTyp.Name - } - - if embed { - t := fTyp.Type - if t.Kind() == reflect.Pointer { - t = t.Elem() - } - indexStructInner(t, path, byName, byPos) - } else { - byName[name] = append(byName[name], structField{ - Name: name, - Path: path, - Tagged: tagName != "", - OmitEmpty: opts.Contains("omitempty"), - Quote: opts.Contains("string") && isQuotable(fTyp.Type), - }) - *byPos = append(*byPos, name) - } - } -} - -func isQuotable(typ reflect.Type) bool { - for typ.Kind() == reflect.Pointer { - typ = typ.Elem() - } - switch typ.Kind() { - case reflect.Bool, - reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, - reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, - reflect.Uintptr, - reflect.Float32, reflect.Float64, - reflect.String: - return true - default: - return false - } -} diff --git a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/06e2c9db80a08b67fad7f1a4606dc7419750995a57828aa25ea57fe7099d5c03 b/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/06e2c9db80a08b67fad7f1a4606dc7419750995a57828aa25ea57fe7099d5c03 deleted file mode 100644 index c3774e7..0000000 --- a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/06e2c9db80a08b67fad7f1a4606dc7419750995a57828aa25ea57fe7099d5c03 +++ /dev/null @@ -1,2 +0,0 @@ -go test fuzz v1 -[]byte("0000000") diff --git a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/24f53a36f8832fec65cac0aa0f3b43ec1c904414fa6d38f6fc288b0bbd69588a b/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/24f53a36f8832fec65cac0aa0f3b43ec1c904414fa6d38f6fc288b0bbd69588a deleted file mode 100644 index 4c861db..0000000 --- a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/24f53a36f8832fec65cac0aa0f3b43ec1c904414fa6d38f6fc288b0bbd69588a +++ /dev/null @@ -1,2 +0,0 @@ -go test fuzz v1 -[]byte("000000000000000000000000000000000") diff --git a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/2d49311ef22319f70a3590a86b406b9f2565987a4a3b6d7660ddc308b5b2fae2 b/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/2d49311ef22319f70a3590a86b406b9f2565987a4a3b6d7660ddc308b5b2fae2 deleted file mode 100644 index 3d32e14..0000000 --- a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/2d49311ef22319f70a3590a86b406b9f2565987a4a3b6d7660ddc308b5b2fae2 +++ /dev/null @@ -1,2 +0,0 @@ -go test fuzz v1 -[]byte("00000000000000000") diff --git a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/356e28f5914a0f16f3cef81330f1d92060be4d694a93dedd654bf48743a7d2bd b/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/356e28f5914a0f16f3cef81330f1d92060be4d694a93dedd654bf48743a7d2bd deleted file mode 100644 index d08ef92..0000000 --- a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/356e28f5914a0f16f3cef81330f1d92060be4d694a93dedd654bf48743a7d2bd +++ /dev/null @@ -1,2 +0,0 @@ -go test fuzz v1 -[]byte("00000000000000000000000000000000") diff --git a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/582528ddfad69eb57775199a43e0f9fd5c94bba343ce7bb6724d4ebafe311ed4 b/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/582528ddfad69eb57775199a43e0f9fd5c94bba343ce7bb6724d4ebafe311ed4 deleted file mode 100644 index a96f559..0000000 --- a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/582528ddfad69eb57775199a43e0f9fd5c94bba343ce7bb6724d4ebafe311ed4 +++ /dev/null @@ -1,2 +0,0 @@ -go test fuzz v1 -[]byte("0") diff --git a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/60c81ee499a7f1e151b66b08f0a4ff81edd7cb53d00dce8ee0eaf31683996026 b/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/60c81ee499a7f1e151b66b08f0a4ff81edd7cb53d00dce8ee0eaf31683996026 deleted file mode 100644 index 87c024d..0000000 --- a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/60c81ee499a7f1e151b66b08f0a4ff81edd7cb53d00dce8ee0eaf31683996026 +++ /dev/null @@ -1,2 +0,0 @@ -go test fuzz v1 -[]byte("0000000000000000000000000000000000000000000000000000") diff --git a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/66498f377f38b53eebe1ceaa4a53e4de01a04efc02ac9cfda60f9815f80e9b9d b/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/66498f377f38b53eebe1ceaa4a53e4de01a04efc02ac9cfda60f9815f80e9b9d deleted file mode 100644 index 959401e..0000000 --- a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/66498f377f38b53eebe1ceaa4a53e4de01a04efc02ac9cfda60f9815f80e9b9d +++ /dev/null @@ -1,2 +0,0 @@ -go test fuzz v1 -[]byte("000000") diff --git a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/731951fe84fa6f3a7f6ee8adaa585d4f6a01f359a04737e51ffc70f16f480b9b b/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/731951fe84fa6f3a7f6ee8adaa585d4f6a01f359a04737e51ffc70f16f480b9b deleted file mode 100644 index bd1ae59..0000000 --- a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/731951fe84fa6f3a7f6ee8adaa585d4f6a01f359a04737e51ffc70f16f480b9b +++ /dev/null @@ -1,2 +0,0 @@ -go test fuzz v1 -[]byte("000000000000000000000000000000000000000000000000") diff --git a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/7d6367ba84cd18550920b5202cd1269174416ce32769c7f59376e76b7dd3129c b/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/7d6367ba84cd18550920b5202cd1269174416ce32769c7f59376e76b7dd3129c deleted file mode 100644 index 09e0ad2..0000000 --- a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/7d6367ba84cd18550920b5202cd1269174416ce32769c7f59376e76b7dd3129c +++ /dev/null @@ -1,2 +0,0 @@ -go test fuzz v1 -[]byte("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") diff --git a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/8727b16d337d7b8187433233f3a90099024e580a6ba319ea2bf539880c50bd7c b/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/8727b16d337d7b8187433233f3a90099024e580a6ba319ea2bf539880c50bd7c deleted file mode 100644 index e8000f3..0000000 --- a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/8727b16d337d7b8187433233f3a90099024e580a6ba319ea2bf539880c50bd7c +++ /dev/null @@ -1,2 +0,0 @@ -go test fuzz v1 -[]byte("00") diff --git a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/9201a772731543760326638b8915f80863feab0ba0108183b3093934bdc0420c b/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/9201a772731543760326638b8915f80863feab0ba0108183b3093934bdc0420c deleted file mode 100644 index aac6b7d..0000000 --- a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/9201a772731543760326638b8915f80863feab0ba0108183b3093934bdc0420c +++ /dev/null @@ -1,2 +0,0 @@ -go test fuzz v1 -[]byte("00000000000000") diff --git a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/92f75f690317ace34aeaae3fe39f5f2ff9830777253ff371c5ef6f403a0f8f0f b/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/92f75f690317ace34aeaae3fe39f5f2ff9830777253ff371c5ef6f403a0f8f0f deleted file mode 100644 index f3bf6d9..0000000 --- a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/92f75f690317ace34aeaae3fe39f5f2ff9830777253ff371c5ef6f403a0f8f0f +++ /dev/null @@ -1,2 +0,0 @@ -go test fuzz v1 -[]byte("00000000000") diff --git a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/93d6f7bc0d93f998c7b7fe654ff46010d6fa76f0a142c3523c42454f8ad10b07 b/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/93d6f7bc0d93f998c7b7fe654ff46010d6fa76f0a142c3523c42454f8ad10b07 deleted file mode 100644 index 2e7f462..0000000 --- a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/93d6f7bc0d93f998c7b7fe654ff46010d6fa76f0a142c3523c42454f8ad10b07 +++ /dev/null @@ -1,2 +0,0 @@ -go test fuzz v1 -[]byte("00000000") diff --git a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/a7450fd77fc7c53cc5bd136874415dddfff5c586e662f21420caa7a94131a56a b/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/a7450fd77fc7c53cc5bd136874415dddfff5c586e662f21420caa7a94131a56a deleted file mode 100644 index c541f52..0000000 --- a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/a7450fd77fc7c53cc5bd136874415dddfff5c586e662f21420caa7a94131a56a +++ /dev/null @@ -1,2 +0,0 @@ -go test fuzz v1 -[]byte("000000000000000000000000000000000000000000000000000") diff --git a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/a95d2a0f87501a643d54218d2ad8112204672cc1fb30be297853616788208a5c b/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/a95d2a0f87501a643d54218d2ad8112204672cc1fb30be297853616788208a5c deleted file mode 100644 index 5d56f29..0000000 --- a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/a95d2a0f87501a643d54218d2ad8112204672cc1fb30be297853616788208a5c +++ /dev/null @@ -1,2 +0,0 @@ -go test fuzz v1 -[]byte("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") diff --git a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/beed435aa2fee4819eab217543561dfd8001d4a44f53ceb664aaba86cebfaf21 b/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/beed435aa2fee4819eab217543561dfd8001d4a44f53ceb664aaba86cebfaf21 deleted file mode 100644 index 4b4d59f..0000000 --- a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/beed435aa2fee4819eab217543561dfd8001d4a44f53ceb664aaba86cebfaf21 +++ /dev/null @@ -1,2 +0,0 @@ -go test fuzz v1 -[]byte("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") diff --git a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/c2501043394e49f2477408be5ef9389790e33ed1886073dec445d4cf05bcd4b4 b/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/c2501043394e49f2477408be5ef9389790e33ed1886073dec445d4cf05bcd4b4 deleted file mode 100644 index ef9f9d4..0000000 --- a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/c2501043394e49f2477408be5ef9389790e33ed1886073dec445d4cf05bcd4b4 +++ /dev/null @@ -1,2 +0,0 @@ -go test fuzz v1 -[]byte("000") diff --git a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/caf81e9797b19c76c1fc4dbf537d4d81f389524539f402d13aa01f93a65ac7e9 b/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/caf81e9797b19c76c1fc4dbf537d4d81f389524539f402d13aa01f93a65ac7e9 deleted file mode 100644 index 67322c7..0000000 --- a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/caf81e9797b19c76c1fc4dbf537d4d81f389524539f402d13aa01f93a65ac7e9 +++ /dev/null @@ -1,2 +0,0 @@ -go test fuzz v1 -[]byte("") diff --git a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/cc90a4a40ae9b3beac70baf6d7821a5a6f3a90cabb033575790be91723593680 b/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/cc90a4a40ae9b3beac70baf6d7821a5a6f3a90cabb033575790be91723593680 deleted file mode 100644 index f195330..0000000 --- a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/cc90a4a40ae9b3beac70baf6d7821a5a6f3a90cabb033575790be91723593680 +++ /dev/null @@ -1,2 +0,0 @@ -go test fuzz v1 -[]byte("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\x04000000000000\r00000000000000000000") diff --git a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/ec72f669d648d8d9b9f75a3b303897c59b11e4bfb7622f25ff251a92f182bc2a b/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/ec72f669d648d8d9b9f75a3b303897c59b11e4bfb7622f25ff251a92f182bc2a deleted file mode 100644 index 5b0d392..0000000 --- a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/ec72f669d648d8d9b9f75a3b303897c59b11e4bfb7622f25ff251a92f182bc2a +++ /dev/null @@ -1,2 +0,0 @@ -go test fuzz v1 -[]byte("0000000000000000000000000000000000000000") diff --git a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/f34630c44c11bb13d27531927c5c1e65d159b70f39cd161da0dba348c1221ab3 b/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/f34630c44c11bb13d27531927c5c1e65d159b70f39cd161da0dba348c1221ab3 deleted file mode 100644 index a389d3c..0000000 --- a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/f34630c44c11bb13d27531927c5c1e65d159b70f39cd161da0dba348c1221ab3 +++ /dev/null @@ -1,2 +0,0 @@ -go test fuzz v1 -[]byte("00000") diff --git a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/fd67efb09d433a1351a201281dbf6568628b4135c35c811dd9bce97620a75d43 b/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/fd67efb09d433a1351a201281dbf6568628b4135c35c811dd9bce97620a75d43 deleted file mode 100644 index 17d10b2..0000000 --- a/lib/lowmemjson/testdata/fuzz/FuzzBase64Decoder/fd67efb09d433a1351a201281dbf6568628b4135c35c811dd9bce97620a75d43 +++ /dev/null @@ -1,2 +0,0 @@ -go test fuzz v1 -[]byte("000000000000") diff --git a/misc.go b/misc.go new file mode 100644 index 0000000..132b177 --- /dev/null +++ b/misc.go @@ -0,0 +1,131 @@ +// Copyright (C) 2022 Luke Shumaker +// +// SPDX-License-Identifier: GPL-2.0-or-later + +package lowmemjson + +import ( + "encoding/json" + "io" + "reflect" + "unicode/utf8" +) + +const Tab = "\t" + +const hex = "0123456789abcdef" + +var ( + numberType = reflect.TypeOf(json.Number("")) + byteType = reflect.TypeOf(byte(0)) + byteSliceType = reflect.TypeOf(([]byte)(nil)) +) + +// generic I/O ///////////////////////////////////////////////////////////////// + +func decodeRune[T interface{ []byte | string }](s T) (r rune, size int) { + iface := any(s) + if str, ok := iface.(string); ok { + return utf8.DecodeRuneInString(str) + } else { + return utf8.DecodeRune(iface.([]byte)) + } +} + +func writeByte(w io.Writer, c byte) error { + if br, ok := w.(interface{ WriteByte(byte) error }); ok { + return br.WriteByte(c) + } + var buf [1]byte + buf[0] = c + if _, err := w.Write(buf[:]); err != nil { + return err + } + return nil +} + +func writeRune(w io.Writer, c rune) (int, error) { + if rw, ok := w.(interface{ WriteRune(rune) (int, error) }); ok { + return rw.WriteRune(c) + } + var buf [utf8.UTFMax]byte + n := utf8.EncodeRune(buf[:], c) + return w.Write(buf[:n]) +} + +// JSON string encoding //////////////////////////////////////////////////////// + +func UnicodeEscapeJSSafe(c rune, _ bool) bool { + // JSON is notionally a JS subset, but that's not actually + // true. + // + // http://timelessrepo.com/json-isnt-a-javascript-subset + switch c { + case '\u2028', '\u2029': + return true + default: + return false + } +} + +func UnicodeEscapeHTMLSafe(c rune, wasEscaped bool) bool { + switch c { + case '&', '<', '>': + return true + default: + return UnicodeEscapeJSSafe(c, wasEscaped) + } +} + +func UnicodeEscapeDefault(c rune, wasEscaped bool) bool { + switch c { + case '\b', '\f', utf8.RuneError: + return true + default: + return UnicodeEscapeHTMLSafe(c, wasEscaped) + } +} + +func writeStringUnicodeEscape(w io.Writer, c rune) (int, error) { + buf := [6]byte{ + '\\', + 'u', + hex[(c>>12)&0xf], + hex[(c>>8)&0xf], + hex[(c>>4)&0xf], + hex[(c>>0)&0xf], + } + return w.Write(buf[:]) +} +func writeStringShortEscape(w io.Writer, c byte) (int, error) { + buf := [2]byte{'\\', c} + return w.Write(buf[:]) +} +func writeStringChar(w io.Writer, c rune, wasEscaped bool, escaper func(rune, bool) bool) (int, error) { + if escaper == nil { + escaper = UnicodeEscapeDefault + } + switch { + case c <= 0xFFFF && escaper(c, wasEscaped): + return writeStringUnicodeEscape(w, c) + case c == '"' || c == '\\': + return writeStringShortEscape(w, byte(c)) + case c < 0x0020: + switch c { + case '\b': + return writeStringShortEscape(w, 'b') + case '\f': + return writeStringShortEscape(w, 'f') + case '\n': + return writeStringShortEscape(w, 'n') + case '\r': + return writeStringShortEscape(w, 'r') + case '\t': + return writeStringShortEscape(w, 't') + default: + return writeStringUnicodeEscape(w, c) + } + default: + return writeRune(w, c) + } +} diff --git a/reencode.go b/reencode.go new file mode 100644 index 0000000..50c8ba3 --- /dev/null +++ b/reencode.go @@ -0,0 +1,598 @@ +// Copyright (C) 2022 Luke Shumaker +// +// SPDX-License-Identifier: GPL-2.0-or-later + +package lowmemjson + +import ( + "errors" + "fmt" + "io" + "unicode/utf8" +) + +type reencodeState func(rune) error + +type ReEncoder struct { + Out io.Writer + + // Whether to minify the JSON. + Compact bool + // String to use to indent; ignored if Compact is true. + Indent string + // String to put before indents, for testing-compat with + // encoding/json only. + prefix string + // Returns whether a given character in a string should be + // "\uXXXX" escaped. The bool argument is whether it was + // \u-escaped in the input. This does not affect characters + // that must or must-not be \u-escaped to be valid JSON. + // + // If not set, then EscapeUnicodeDefault is used. + UnicodeEscape func(rune, bool) bool + + bailAfterCurrent bool + + // state: .Write's utf8-decoding buffer + buf [utf8.UTFMax]byte + bufLen int + + // state: .WriteRune + err error + inputPos int64 + written int + stack []reencodeState + stack0IsNumber bool + curIndent int + + // state: reencodeState-specific + stateBuf []byte +} + +// public API ////////////////////////////////////////////////////////////////// + +func (enc *ReEncoder) Write(p []byte) (int, error) { + if len(p) == 0 { + return 0, nil + } + var n int + if enc.bufLen > 0 { + copy(enc.buf[enc.bufLen:], p) + c, size := utf8.DecodeRune(enc.buf[:]) + n += size - enc.bufLen + enc.bufLen = 0 + if _, err := enc.WriteRune(c); err != nil { + return 0, err + } + } + for utf8.FullRune(p[n:]) { + c, size := utf8.DecodeRune(p[n:]) + if _, err := enc.WriteRune(c); err != nil { + return n, err + } + n += size + } + enc.bufLen = copy(enc.buf[:], p[n:]) + return len(p), nil +} + +func (enc *ReEncoder) Flush() error { + if enc.bufLen > 0 { + return &SyntaxError{fmt.Sprintf("EOF: unflushed unicode garbage: %q", enc.buf[:enc.bufLen]), enc.inputPos} + } + switch len(enc.stack) { + case 0: + return nil + case 1: + if enc.stack0IsNumber { + enc.Compact = true + return enc.state('\n') + } + fallthrough + default: + return &SyntaxError{fmt.Sprintf("EOF: in the middle of a value"), enc.inputPos} + } +} + +func (enc *ReEncoder) WriteRune(c rune) (n int, err error) { + if enc.err != nil { + return 0, enc.err + } + if enc.bufLen != 0 { + enc.err = errors.New("lowmemjson.ReEncoder: cannot .WriteRune() when there is a partial rune that has been .Write()n") + return 0, enc.err + } + enc.written = 0 + enc.err = enc.state(c) + enc.inputPos += int64(utf8.RuneLen(c)) + return enc.written, enc.err +} + +// io helpers ////////////////////////////////////////////////////////////////// + +func (enc *ReEncoder) emitByte(c byte) error { + err := writeByte(enc.Out, c) + if err == nil { + enc.written++ + } + return err +} + +func (enc *ReEncoder) emit(n int, err error) error { + enc.written += n + return err +} + +func (enc *ReEncoder) nlIndent() error { + if enc.Compact || enc.Indent == "" { + return nil + } + if err := enc.emitByte('\n'); err != nil { + return err + } + if enc.prefix != "" { + if err := enc.emit(io.WriteString(enc.Out, enc.prefix)); err != nil { + return err + } + } + for i := 0; i < enc.curIndent; i++ { + if err := enc.emit(io.WriteString(enc.Out, enc.Indent)); err != nil { + return err + } + } + return nil +} + +// state helpers /////////////////////////////////////////////////////////////// + +func (enc *ReEncoder) pushState(state reencodeState, isNumber bool) { + if len(enc.stack) == 0 { + enc.stack0IsNumber = isNumber + } + enc.stack = append(enc.stack, state) +} +func (enc *ReEncoder) replaceState(state reencodeState, isNumber bool) { + if len(enc.stack) == 1 { + enc.stack0IsNumber = isNumber + } + enc.stack[len(enc.stack)-1] = state +} +func (enc *ReEncoder) popState() { + if len(enc.stack) == 1 { + enc.stack0IsNumber = false + } + enc.stack = enc.stack[:len(enc.stack)-1] +} + +var errBailedAfterCurrent = errors.New("bailed after current") + +func (enc *ReEncoder) state(c rune) error { + if len(enc.stack) == 0 { + if enc.bailAfterCurrent { + return errBailedAfterCurrent + } + enc.pushState(enc.stateAny, false) + } + return enc.stack[len(enc.stack)-1](c) +} + +// any ///////////////////////////////////////////////////////////////////////////////////////////// + +func (enc *ReEncoder) stateAny(c rune) error { + switch c { + case 0x0020, 0x000A, 0x000D, 0x0009: + if enc.Compact || enc.Indent != "" { + return nil + } + case '{': + enc.replaceState(enc.stateInEmptyObject, false) + enc.curIndent++ + case '[': + enc.replaceState(enc.stateInEmptyArray, false) + enc.curIndent++ + case '"': + enc.replaceState(enc.stateInString, false) + case '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + enc.replaceState(enc.stateNumberA, true) + return enc.state(c) + case 't': + enc.replaceState(enc.stateInTrue, false) + enc.stateBuf = append(enc.stateBuf[:0], 't') + case 'f': + enc.replaceState(enc.stateInFalse, false) + enc.stateBuf = append(enc.stateBuf[:0], 'f') + case 'n': + enc.replaceState(enc.stateInNull, false) + enc.stateBuf = append(enc.stateBuf[:0], 'n') + default: + return &SyntaxError{fmt.Sprintf("any: unexpected character: %c", c), enc.inputPos} + } + return enc.emitByte(byte(c)) +} + +// object ////////////////////////////////////////////////////////////////////////////////////////// + +func (enc *ReEncoder) stateInEmptyObject(c rune) error { return enc._stateInObject(c, false) } +func (enc *ReEncoder) stateInNonEmptyObject(c rune) error { return enc._stateInObject(c, true) } +func (enc *ReEncoder) _stateInObject(c rune, nonempty bool) error { + switch c { + case 0x0020, 0x000A, 0x000D, 0x0009: + if enc.Compact || enc.Indent != "" { + return nil + } + case '"': + if err := enc.nlIndent(); err != nil { + return err + } + enc.replaceState(enc.stateInKV, false) + enc.pushState(enc.stateInString, false) + case '}': + enc.popState() + enc.curIndent-- + if nonempty { + if err := enc.nlIndent(); err != nil { + return err + } + } + default: + return &SyntaxError{fmt.Sprintf("object: unexpected character: %c", c), enc.inputPos} + } + return enc.emitByte(byte(c)) +} +func (enc *ReEncoder) stateInKV(c rune) error { + switch c { + case 0x0020, 0x000A, 0x000D, 0x0009: + if enc.Compact || enc.Indent != "" { + return nil + } + return enc.emitByte(byte(c)) + case ':': + enc.replaceState(enc.stateAfterV, false) + enc.pushState(enc.stateAny, false) + if err := enc.emitByte(byte(c)); err != nil { + return err + } + if !enc.Compact && enc.Indent != "" { + return enc.emitByte(' ') + } + return nil + default: + return &SyntaxError{fmt.Sprintf("object member: unexpected character: %c", c), enc.inputPos} + } +} +func (enc *ReEncoder) stateAfterV(c rune) error { + switch c { + case 0x0020, 0x000A, 0x000D, 0x0009: + if enc.Compact || enc.Indent != "" { + return nil + } + case ',': + enc.replaceState(enc.stateInNonEmptyObject, false) + case '}': + enc.popState() + enc.curIndent-- + if err := enc.nlIndent(); err != nil { + return err + } + default: + return &SyntaxError{fmt.Sprintf("object member: unexpected character: %c", c), enc.inputPos} + } + return enc.emitByte(byte(c)) +} + +// array /////////////////////////////////////////////////////////////////////////////////////////// + +func (enc *ReEncoder) stateInEmptyArray(c rune) error { return enc._stateInArray(c, false) } +func (enc *ReEncoder) stateInNonEmptyArray(c rune) error { return enc._stateInArray(c, true) } +func (enc *ReEncoder) _stateInArray(c rune, nonempty bool) error { + switch c { + case 0x0020, 0x000A, 0x000D, 0x0009: + if enc.Compact || enc.Indent != "" { + return nil + } + case ']': + enc.popState() + enc.curIndent-- + if nonempty { + if err := enc.nlIndent(); err != nil { + return err + } + } + default: + if err := enc.nlIndent(); err != nil { + return err + } + enc.replaceState(enc.stateAfterItem, false) + enc.pushState(enc.stateAny, false) + return enc.state(c) + } + return enc.emitByte(byte(c)) +} +func (enc *ReEncoder) stateAfterItem(c rune) error { + switch c { + case 0x0020, 0x000A, 0x000D, 0x0009: + if enc.Compact || enc.Indent != "" { + return nil + } + case ',': + enc.replaceState(enc.stateInNonEmptyArray, false) + case ']': + enc.popState() + enc.curIndent-- + if err := enc.nlIndent(); err != nil { + return err + } + default: + return &SyntaxError{fmt.Sprintf("array: unexpected character: %c", c), enc.inputPos} + } + return enc.emitByte(byte(c)) +} + +// string ////////////////////////////////////////////////////////////////////////////////////////// + +func (enc *ReEncoder) stateInString(c rune) error { + switch { + case c == '\\': + enc.replaceState(enc.stateInBackslash, false) + return nil + case c == '"': + enc.popState() + return enc.emitByte(byte(c)) + case 0x0020 <= c && c <= 0x10FFFF: + return enc.emit(writeStringChar(enc.Out, c, false, enc.UnicodeEscape)) + default: + return &SyntaxError{fmt.Sprintf("string: unexpected character: %c", c), enc.inputPos} + } +} +func (enc *ReEncoder) stateInBackslash(c rune) error { + switch c { + case '"': + enc.replaceState(enc.stateInString, false) + return enc.emit(writeStringChar(enc.Out, '"', false, enc.UnicodeEscape)) + case '\\': + enc.replaceState(enc.stateInString, false) + return enc.emit(writeStringChar(enc.Out, '\\', false, enc.UnicodeEscape)) + case '/': + enc.replaceState(enc.stateInString, false) + return enc.emit(writeStringChar(enc.Out, '/', false, enc.UnicodeEscape)) + case 'b': + enc.replaceState(enc.stateInString, false) + return enc.emit(writeStringChar(enc.Out, '\b', false, enc.UnicodeEscape)) + case 'f': + enc.replaceState(enc.stateInString, false) + return enc.emit(writeStringChar(enc.Out, '\f', false, enc.UnicodeEscape)) + case 'n': + enc.replaceState(enc.stateInString, false) + return enc.emit(writeStringChar(enc.Out, '\n', false, enc.UnicodeEscape)) + case 'r': + enc.replaceState(enc.stateInString, false) + return enc.emit(writeStringChar(enc.Out, '\r', false, enc.UnicodeEscape)) + case 't': + enc.replaceState(enc.stateInString, false) + return enc.emit(writeStringChar(enc.Out, '\t', false, enc.UnicodeEscape)) + case 'u': + enc.replaceState(enc.stateInUnicode, false) + return nil + default: + return &SyntaxError{fmt.Sprintf("string backslash sequence: unexpected character: %c", c), enc.inputPos} + } +} +func (enc *ReEncoder) stateInUnicode(c rune) error { + switch { + case '0' <= c && c <= '9': + enc.stateBuf = append(enc.stateBuf, byte(c)-'0') + case 'a' <= c && c <= 'f': + enc.stateBuf = append(enc.stateBuf, byte(c)-'a'+10) + case 'A' <= c && c <= 'F': + enc.stateBuf = append(enc.stateBuf, byte(c)-'A'+10) + default: + return &SyntaxError{fmt.Sprintf("string unicode sequence: unexpected character: %c", c), enc.inputPos} + } + if len(enc.stateBuf) == 4 { + enc.replaceState(enc.stateInString, false) + c := 0 | + rune(enc.stateBuf[0])<<12 | + rune(enc.stateBuf[1])<<8 | + rune(enc.stateBuf[2])<<4 | + rune(enc.stateBuf[3])<<0 + enc.stateBuf = enc.stateBuf[:0] + return enc.emit(writeStringChar(enc.Out, c, true, enc.UnicodeEscape)) + } + return nil +} + +// number ////////////////////////////////////////////////////////////////////////////////////////// + +// Here's a flattened drawing of the syntax diagram from www.json.org : +// +// [------------ integer ----------][-- fraction ---][-------- exponent -------] +// >─╮─────╭─╮─"0"───────╭─────────╭──╮─────────────╭──╮───────────────────────╭─> +// │ │ │ │ │ │ │ │ │ +// ╰─"-"─╯ ╰─digit 1-9─╯─╭digit╮─╯ ╰─"."─╭digit╮─╯ ╰─"e"─╭─╮─────╭─╭digit╮─╯ +// ╰──<──╯ ╰──<──╯ │ │ │ │ ╰──<──╯ +// ╰─"E"─╯ ╰─"-"─╯ +// │ │ +// ╰─"+"─╯ +// +// Now here it is slightly redrawn, and with each distinct state our +// decoder can be in marked with a single-capital-letter: +// +// [-------------- integer ------------][--------- fraction --------][--------- exponent ---------] +// >─A─╮───────╭──╮─"0"─────────C─╭─────────╮──────────────────╭─────────╮──────────────────────────╭─> +// │ │ │ │ │ │ │ │ +// ╰─"-"─B─╯ ╰─digit 1-9─╭─D─╯─digit╮ ╰─"."─E─digit──╭─F─╯─digit╮ ╰─"e"─╭─G─╮─────╭─╭digit─H─╯ +// ╰────<─────╯ ╰────<─────╯ │ │ │ │ ╰────<───╯ +// ╰─"E"─╯ ╰─"-"─╯ +// │ │ +// ╰─"+"─╯ +// +// Which state we're at is the 'X' in 'stateNumberX'. +// +// Besides just traversing that, there are a few compressions we want to make: +// +// - trim trailing 0s from fraction the (but don't remove the +// fraction if it's all 0s); do this by making the F state a little +// special. This requires a little more state, because when we +// encounter the 0 we don't yet know if it's trailing. So, store +// the number of maybe-trailing zeros in enc.stateBuf[0]; if that +// reaches 255, then bleed over to enc.stateBuf[1] and so on. +// +// - trim leading 0s from the exponent (but don't remove the exponent +// if it's all 0s); do this by making the H state a little special. +// Record whether we've seen a non-zero digit in enc.stateBuf[0] +// (0=false, 1=true). + +// integer-part //////////////////////////////////////////////////////////////// +func (enc *ReEncoder) stateNumberA(c rune) error { // start + switch c { + case '-': + enc.replaceState(enc.stateNumberB, true) + case '0': + enc.replaceState(enc.stateNumberC, true) + case '1', '2', '3', '4', '5', '6', '7', '8', '9': + enc.replaceState(enc.stateNumberD, true) + default: + return &SyntaxError{fmt.Sprintf("number: unexpected character: %c", c), enc.inputPos} + } + return enc.emitByte(byte(c)) +} +func (enc *ReEncoder) stateNumberB(c rune) error { // got a leading "-" + switch c { + case '0': + enc.replaceState(enc.stateNumberC, true) + case '1', '2', '3', '4', '5', '6', '7', '8', '9': + enc.replaceState(enc.stateNumberD, true) + default: + return &SyntaxError{fmt.Sprintf("number: unexpected character: %c", c), enc.inputPos} + } + return enc.emitByte(byte(c)) +} +func (enc *ReEncoder) stateNumberC(c rune) error { // ready for the fraction or exponent part to start + switch c { + case '.': + enc.replaceState(enc.stateNumberE, true) + return enc.emitByte('.') + case 'e', 'E': + enc.replaceState(enc.stateNumberG, true) + enc.stateBuf = append(enc.stateBuf[:0], 0) + return enc.emitByte('e') + default: + enc.popState() + return enc.state(c) + } +} +func (enc *ReEncoder) stateNumberD(c rune) error { // in the integer part + switch c { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + return enc.emitByte(byte(c)) + case '.': + enc.replaceState(enc.stateNumberE, true) + return enc.emitByte('.') + case 'e', 'E': + enc.replaceState(enc.stateNumberG, true) + enc.stateBuf = append(enc.stateBuf[:0], 0) + return enc.emitByte('e') + default: + enc.popState() + return enc.state(c) + } +} + +// fraction-part /////////////////////////////////////////////////////////////// +func (enc *ReEncoder) stateNumberE(c rune) error { // got a ".", ready to read a number for the fraction part + switch c { + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + enc.replaceState(enc.stateNumberF, true) + return enc.emitByte(byte(c)) + default: + return &SyntaxError{fmt.Sprintf("number: unexpected character: %c", c), enc.inputPos} + } +} +func (enc *ReEncoder) stateNumberF(c rune) error { // in the fraction part + switch c { + case '0': + if len(enc.stateBuf) > 0 && enc.stateBuf[len(enc.stateBuf)-1] < 255 { + enc.stateBuf[len(enc.stateBuf)-1]++ + } else { + enc.stateBuf = append(enc.stateBuf, 1) + } + return nil + case '1', '2', '3', '4', '5', '6', '7', '8', '9': + for len(enc.stateBuf) > 0 { + if err := enc.emitByte('0'); err != nil { + return err + } + if enc.stateBuf[len(enc.stateBuf)-1] == 1 { + enc.stateBuf = enc.stateBuf[:len(enc.stateBuf)-1] + } else { + enc.stateBuf[len(enc.stateBuf)-1]-- + } + } + return enc.emitByte(byte(c)) + case 'e', 'E': + enc.replaceState(enc.stateNumberG, true) + enc.stateBuf = append(enc.stateBuf[:0], 0) + return enc.emitByte('e') + default: + enc.stateBuf = enc.stateBuf[:0] + enc.popState() + return enc.state(c) + } +} + +// exponent-part /////////////////////////////////////////////////////////////// +func (enc *ReEncoder) stateNumberG(c rune) error { // got a leading "e" + switch c { + case '-', '+': + enc.replaceState(enc.stateNumberH, true) + return enc.emitByte(byte(c)) + case '0': + enc.replaceState(enc.stateNumberH, true) + return nil + case '1', '2', '3', '4', '5', '6', '7', '8', '9': + enc.replaceState(enc.stateNumberH, true) + enc.stateBuf[0] = 1 + return enc.emitByte(byte(c)) + default: + enc.stateBuf = enc.stateBuf[:0] + return &SyntaxError{fmt.Sprintf("number: unexpected character: %c", c), enc.inputPos} + } +} +func (enc *ReEncoder) stateNumberH(c rune) error { // in the exponent's number part + switch c { + case '0': + if enc.stateBuf[0] == 0 { + return nil + } + return enc.emitByte('0') + case '1', '2', '3', '4', '5', '6', '7', '8', '9': + enc.stateBuf[0] = 1 + return enc.emitByte(byte(c)) + default: + if enc.stateBuf[0] == 0 { + if err := enc.emitByte('0'); err != nil { + return err + } + } + enc.stateBuf = enc.stateBuf[:0] + enc.popState() + return enc.state(c) + } +} + +// literals //////////////////////////////////////////////////////////////////////////////////////// + +func (enc *ReEncoder) stateInTrue(c rune) error { return enc._stateInLiteral(c, "true") } +func (enc *ReEncoder) stateInFalse(c rune) error { return enc._stateInLiteral(c, "false") } +func (enc *ReEncoder) stateInNull(c rune) error { return enc._stateInLiteral(c, "null") } +func (enc *ReEncoder) _stateInLiteral(c rune, full string) error { + if c != rune(full[len(enc.stateBuf)]) { + return &SyntaxError{fmt.Sprintf("%s: unexpected character: %c", full, c), enc.inputPos} + } + enc.stateBuf = append(enc.stateBuf, byte(c)) + if len(enc.stateBuf) == len(full) { + enc.stateBuf = enc.stateBuf[:0] + enc.popState() + } + return enc.emitByte(byte(c)) +} diff --git a/struct.go b/struct.go new file mode 100644 index 0000000..ad142d6 --- /dev/null +++ b/struct.go @@ -0,0 +1,164 @@ +// Copyright (C) 2022 Luke Shumaker +// +// SPDX-License-Identifier: GPL-2.0-or-later + +package lowmemjson + +import ( + "reflect" +) + +type structField struct { + Name string + Path []int + Tagged bool + OmitEmpty bool + Quote bool +} + +type structIndex struct { + byPos []structField + byName map[string]int +} + +func indexStruct(typ reflect.Type) structIndex { + byName := make(map[string][]structField) + var byPos []string + + indexStructInner(typ, nil, byName, &byPos) + + ret := structIndex{ + byName: make(map[string]int), + } + + for _, name := range byPos { + fields := byName[name] + delete(byName, name) + switch len(fields) { + case 0: + // do nothing + case 1: + ret.byName[name] = len(ret.byPos) + ret.byPos = append(ret.byPos, fields[0]) + default: + // To quote the encoding/json docs (version 1.18.4): + // + // If there are multiple fields at the same level, and that level is the + // least nested (and would therefore be the nesting level selected by the + // usual Go rules), the following extra rules apply: + // + // 1) Of those fields, if any are JSON-tagged, only tagged fields are + // considered, even if there are multiple untagged fields that would + // otherwise conflict. + // + // 2) If there is exactly one field (tagged or not according to the first + // rule), that is selected. + // + // 3) Otherwise there are multiple fields, and all are ignored; no error + // occurs. + leastLevel := len(fields[0].Path) + for _, field := range fields[1:] { + if len(field.Path) < leastLevel { + leastLevel = len(field.Path) + } + } + var numUntagged, numTagged int + var untaggedIdx, taggedIdx int + for i, field := range fields { + if len(field.Path) != leastLevel { + continue + } + if field.Tagged { + numTagged++ + taggedIdx = i + if numTagged > 1 { + break // optimization + } + } else { + numUntagged++ + untaggedIdx = i + } + } + switch numTagged { + case 0: + switch numUntagged { + case 0: + // do nothing + case 1: + ret.byName[name] = len(ret.byPos) + ret.byPos = append(ret.byPos, fields[untaggedIdx]) + } + case 1: + ret.byName[name] = len(ret.byPos) + ret.byPos = append(ret.byPos, fields[taggedIdx]) + } + } + } + + return ret +} + +func indexStructInner(typ reflect.Type, prefix []int, byName map[string][]structField, byPos *[]string) { + n := typ.NumField() + for i := 0; i < n; i++ { + path := append(append([]int(nil), prefix...), i) + + fTyp := typ.Field(i) + var embed bool + if fTyp.Anonymous { + t := fTyp.Type + if t.Kind() == reflect.Pointer { + t = t.Elem() + } + if !fTyp.IsExported() && t.Kind() != reflect.Struct { + continue + } + embed = t.Kind() == reflect.Struct + } else if !fTyp.IsExported() { + continue + } + tag := fTyp.Tag.Get("json") + if tag == "-" { + continue + } + tagName, opts := parseTag(tag) + name := tagName + if name == "" { + name = fTyp.Name + } + + if embed { + t := fTyp.Type + if t.Kind() == reflect.Pointer { + t = t.Elem() + } + indexStructInner(t, path, byName, byPos) + } else { + byName[name] = append(byName[name], structField{ + Name: name, + Path: path, + Tagged: tagName != "", + OmitEmpty: opts.Contains("omitempty"), + Quote: opts.Contains("string") && isQuotable(fTyp.Type), + }) + *byPos = append(*byPos, name) + } + } +} + +func isQuotable(typ reflect.Type) bool { + for typ.Kind() == reflect.Pointer { + typ = typ.Elem() + } + switch typ.Kind() { + case reflect.Bool, + reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, + reflect.Uintptr, + reflect.Float32, reflect.Float64, + reflect.String: + return true + default: + return false + } +} diff --git a/testdata/fuzz/FuzzBase64Decoder/06e2c9db80a08b67fad7f1a4606dc7419750995a57828aa25ea57fe7099d5c03 b/testdata/fuzz/FuzzBase64Decoder/06e2c9db80a08b67fad7f1a4606dc7419750995a57828aa25ea57fe7099d5c03 new file mode 100644 index 0000000..c3774e7 --- /dev/null +++ b/testdata/fuzz/FuzzBase64Decoder/06e2c9db80a08b67fad7f1a4606dc7419750995a57828aa25ea57fe7099d5c03 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("0000000") diff --git a/testdata/fuzz/FuzzBase64Decoder/24f53a36f8832fec65cac0aa0f3b43ec1c904414fa6d38f6fc288b0bbd69588a b/testdata/fuzz/FuzzBase64Decoder/24f53a36f8832fec65cac0aa0f3b43ec1c904414fa6d38f6fc288b0bbd69588a new file mode 100644 index 0000000..4c861db --- /dev/null +++ b/testdata/fuzz/FuzzBase64Decoder/24f53a36f8832fec65cac0aa0f3b43ec1c904414fa6d38f6fc288b0bbd69588a @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("000000000000000000000000000000000") diff --git a/testdata/fuzz/FuzzBase64Decoder/2d49311ef22319f70a3590a86b406b9f2565987a4a3b6d7660ddc308b5b2fae2 b/testdata/fuzz/FuzzBase64Decoder/2d49311ef22319f70a3590a86b406b9f2565987a4a3b6d7660ddc308b5b2fae2 new file mode 100644 index 0000000..3d32e14 --- /dev/null +++ b/testdata/fuzz/FuzzBase64Decoder/2d49311ef22319f70a3590a86b406b9f2565987a4a3b6d7660ddc308b5b2fae2 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("00000000000000000") diff --git a/testdata/fuzz/FuzzBase64Decoder/356e28f5914a0f16f3cef81330f1d92060be4d694a93dedd654bf48743a7d2bd b/testdata/fuzz/FuzzBase64Decoder/356e28f5914a0f16f3cef81330f1d92060be4d694a93dedd654bf48743a7d2bd new file mode 100644 index 0000000..d08ef92 --- /dev/null +++ b/testdata/fuzz/FuzzBase64Decoder/356e28f5914a0f16f3cef81330f1d92060be4d694a93dedd654bf48743a7d2bd @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("00000000000000000000000000000000") diff --git a/testdata/fuzz/FuzzBase64Decoder/582528ddfad69eb57775199a43e0f9fd5c94bba343ce7bb6724d4ebafe311ed4 b/testdata/fuzz/FuzzBase64Decoder/582528ddfad69eb57775199a43e0f9fd5c94bba343ce7bb6724d4ebafe311ed4 new file mode 100644 index 0000000..a96f559 --- /dev/null +++ b/testdata/fuzz/FuzzBase64Decoder/582528ddfad69eb57775199a43e0f9fd5c94bba343ce7bb6724d4ebafe311ed4 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("0") diff --git a/testdata/fuzz/FuzzBase64Decoder/60c81ee499a7f1e151b66b08f0a4ff81edd7cb53d00dce8ee0eaf31683996026 b/testdata/fuzz/FuzzBase64Decoder/60c81ee499a7f1e151b66b08f0a4ff81edd7cb53d00dce8ee0eaf31683996026 new file mode 100644 index 0000000..87c024d --- /dev/null +++ b/testdata/fuzz/FuzzBase64Decoder/60c81ee499a7f1e151b66b08f0a4ff81edd7cb53d00dce8ee0eaf31683996026 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("0000000000000000000000000000000000000000000000000000") diff --git a/testdata/fuzz/FuzzBase64Decoder/66498f377f38b53eebe1ceaa4a53e4de01a04efc02ac9cfda60f9815f80e9b9d b/testdata/fuzz/FuzzBase64Decoder/66498f377f38b53eebe1ceaa4a53e4de01a04efc02ac9cfda60f9815f80e9b9d new file mode 100644 index 0000000..959401e --- /dev/null +++ b/testdata/fuzz/FuzzBase64Decoder/66498f377f38b53eebe1ceaa4a53e4de01a04efc02ac9cfda60f9815f80e9b9d @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("000000") diff --git a/testdata/fuzz/FuzzBase64Decoder/731951fe84fa6f3a7f6ee8adaa585d4f6a01f359a04737e51ffc70f16f480b9b b/testdata/fuzz/FuzzBase64Decoder/731951fe84fa6f3a7f6ee8adaa585d4f6a01f359a04737e51ffc70f16f480b9b new file mode 100644 index 0000000..bd1ae59 --- /dev/null +++ b/testdata/fuzz/FuzzBase64Decoder/731951fe84fa6f3a7f6ee8adaa585d4f6a01f359a04737e51ffc70f16f480b9b @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("000000000000000000000000000000000000000000000000") diff --git a/testdata/fuzz/FuzzBase64Decoder/7d6367ba84cd18550920b5202cd1269174416ce32769c7f59376e76b7dd3129c b/testdata/fuzz/FuzzBase64Decoder/7d6367ba84cd18550920b5202cd1269174416ce32769c7f59376e76b7dd3129c new file mode 100644 index 0000000..09e0ad2 --- /dev/null +++ b/testdata/fuzz/FuzzBase64Decoder/7d6367ba84cd18550920b5202cd1269174416ce32769c7f59376e76b7dd3129c @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") diff --git a/testdata/fuzz/FuzzBase64Decoder/8727b16d337d7b8187433233f3a90099024e580a6ba319ea2bf539880c50bd7c b/testdata/fuzz/FuzzBase64Decoder/8727b16d337d7b8187433233f3a90099024e580a6ba319ea2bf539880c50bd7c new file mode 100644 index 0000000..e8000f3 --- /dev/null +++ b/testdata/fuzz/FuzzBase64Decoder/8727b16d337d7b8187433233f3a90099024e580a6ba319ea2bf539880c50bd7c @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("00") diff --git a/testdata/fuzz/FuzzBase64Decoder/9201a772731543760326638b8915f80863feab0ba0108183b3093934bdc0420c b/testdata/fuzz/FuzzBase64Decoder/9201a772731543760326638b8915f80863feab0ba0108183b3093934bdc0420c new file mode 100644 index 0000000..aac6b7d --- /dev/null +++ b/testdata/fuzz/FuzzBase64Decoder/9201a772731543760326638b8915f80863feab0ba0108183b3093934bdc0420c @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("00000000000000") diff --git a/testdata/fuzz/FuzzBase64Decoder/92f75f690317ace34aeaae3fe39f5f2ff9830777253ff371c5ef6f403a0f8f0f b/testdata/fuzz/FuzzBase64Decoder/92f75f690317ace34aeaae3fe39f5f2ff9830777253ff371c5ef6f403a0f8f0f new file mode 100644 index 0000000..f3bf6d9 --- /dev/null +++ b/testdata/fuzz/FuzzBase64Decoder/92f75f690317ace34aeaae3fe39f5f2ff9830777253ff371c5ef6f403a0f8f0f @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("00000000000") diff --git a/testdata/fuzz/FuzzBase64Decoder/93d6f7bc0d93f998c7b7fe654ff46010d6fa76f0a142c3523c42454f8ad10b07 b/testdata/fuzz/FuzzBase64Decoder/93d6f7bc0d93f998c7b7fe654ff46010d6fa76f0a142c3523c42454f8ad10b07 new file mode 100644 index 0000000..2e7f462 --- /dev/null +++ b/testdata/fuzz/FuzzBase64Decoder/93d6f7bc0d93f998c7b7fe654ff46010d6fa76f0a142c3523c42454f8ad10b07 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("00000000") diff --git a/testdata/fuzz/FuzzBase64Decoder/a7450fd77fc7c53cc5bd136874415dddfff5c586e662f21420caa7a94131a56a b/testdata/fuzz/FuzzBase64Decoder/a7450fd77fc7c53cc5bd136874415dddfff5c586e662f21420caa7a94131a56a new file mode 100644 index 0000000..c541f52 --- /dev/null +++ b/testdata/fuzz/FuzzBase64Decoder/a7450fd77fc7c53cc5bd136874415dddfff5c586e662f21420caa7a94131a56a @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("000000000000000000000000000000000000000000000000000") diff --git a/testdata/fuzz/FuzzBase64Decoder/a95d2a0f87501a643d54218d2ad8112204672cc1fb30be297853616788208a5c b/testdata/fuzz/FuzzBase64Decoder/a95d2a0f87501a643d54218d2ad8112204672cc1fb30be297853616788208a5c new file mode 100644 index 0000000..5d56f29 --- /dev/null +++ b/testdata/fuzz/FuzzBase64Decoder/a95d2a0f87501a643d54218d2ad8112204672cc1fb30be297853616788208a5c @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") diff --git a/testdata/fuzz/FuzzBase64Decoder/beed435aa2fee4819eab217543561dfd8001d4a44f53ceb664aaba86cebfaf21 b/testdata/fuzz/FuzzBase64Decoder/beed435aa2fee4819eab217543561dfd8001d4a44f53ceb664aaba86cebfaf21 new file mode 100644 index 0000000..4b4d59f --- /dev/null +++ b/testdata/fuzz/FuzzBase64Decoder/beed435aa2fee4819eab217543561dfd8001d4a44f53ceb664aaba86cebfaf21 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") diff --git a/testdata/fuzz/FuzzBase64Decoder/c2501043394e49f2477408be5ef9389790e33ed1886073dec445d4cf05bcd4b4 b/testdata/fuzz/FuzzBase64Decoder/c2501043394e49f2477408be5ef9389790e33ed1886073dec445d4cf05bcd4b4 new file mode 100644 index 0000000..ef9f9d4 --- /dev/null +++ b/testdata/fuzz/FuzzBase64Decoder/c2501043394e49f2477408be5ef9389790e33ed1886073dec445d4cf05bcd4b4 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("000") diff --git a/testdata/fuzz/FuzzBase64Decoder/caf81e9797b19c76c1fc4dbf537d4d81f389524539f402d13aa01f93a65ac7e9 b/testdata/fuzz/FuzzBase64Decoder/caf81e9797b19c76c1fc4dbf537d4d81f389524539f402d13aa01f93a65ac7e9 new file mode 100644 index 0000000..67322c7 --- /dev/null +++ b/testdata/fuzz/FuzzBase64Decoder/caf81e9797b19c76c1fc4dbf537d4d81f389524539f402d13aa01f93a65ac7e9 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("") diff --git a/testdata/fuzz/FuzzBase64Decoder/cc90a4a40ae9b3beac70baf6d7821a5a6f3a90cabb033575790be91723593680 b/testdata/fuzz/FuzzBase64Decoder/cc90a4a40ae9b3beac70baf6d7821a5a6f3a90cabb033575790be91723593680 new file mode 100644 index 0000000..f195330 --- /dev/null +++ b/testdata/fuzz/FuzzBase64Decoder/cc90a4a40ae9b3beac70baf6d7821a5a6f3a90cabb033575790be91723593680 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\x04000000000000\r00000000000000000000") diff --git a/testdata/fuzz/FuzzBase64Decoder/ec72f669d648d8d9b9f75a3b303897c59b11e4bfb7622f25ff251a92f182bc2a b/testdata/fuzz/FuzzBase64Decoder/ec72f669d648d8d9b9f75a3b303897c59b11e4bfb7622f25ff251a92f182bc2a new file mode 100644 index 0000000..5b0d392 --- /dev/null +++ b/testdata/fuzz/FuzzBase64Decoder/ec72f669d648d8d9b9f75a3b303897c59b11e4bfb7622f25ff251a92f182bc2a @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("0000000000000000000000000000000000000000") diff --git a/testdata/fuzz/FuzzBase64Decoder/f34630c44c11bb13d27531927c5c1e65d159b70f39cd161da0dba348c1221ab3 b/testdata/fuzz/FuzzBase64Decoder/f34630c44c11bb13d27531927c5c1e65d159b70f39cd161da0dba348c1221ab3 new file mode 100644 index 0000000..a389d3c --- /dev/null +++ b/testdata/fuzz/FuzzBase64Decoder/f34630c44c11bb13d27531927c5c1e65d159b70f39cd161da0dba348c1221ab3 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("00000") diff --git a/testdata/fuzz/FuzzBase64Decoder/fd67efb09d433a1351a201281dbf6568628b4135c35c811dd9bce97620a75d43 b/testdata/fuzz/FuzzBase64Decoder/fd67efb09d433a1351a201281dbf6568628b4135c35c811dd9bce97620a75d43 new file mode 100644 index 0000000..17d10b2 --- /dev/null +++ b/testdata/fuzz/FuzzBase64Decoder/fd67efb09d433a1351a201281dbf6568628b4135c35c811dd9bce97620a75d43 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("000000000000") diff --git a/tools/.gitignore b/tools/.gitignore new file mode 100644 index 0000000..ae3c172 --- /dev/null +++ b/tools/.gitignore @@ -0,0 +1 @@ +/bin/ -- cgit v1.2.3-2-g168b