From patchwork Fri Oct 20 10:00:43 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Dodji Seketeli X-Patchwork-Id: 78215 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 2FF313858D39 for ; Fri, 20 Oct 2023 10:00:57 +0000 (GMT) X-Original-To: libabigail@sourceware.org Delivered-To: libabigail@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTPS id 19C503858C52 for ; Fri, 20 Oct 2023 10:00:50 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 19C503858C52 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 19C503858C52 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1697796053; cv=none; b=drHh4G3UlZLxic+twAAvsWK13vIYDI46SE3ELhAkf/xjSP87b0SHqe/TH/DvmnTWDO4txLtrWEcKMqOxrqoP8kTCgksuL9ARovjNKi9ljqeX3nql0t/mLhYZd8zP8+rlWBdFR9j4TOd3KCfhifCk0yOPw7WZ0/wpn7ya2QdjqYk= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1697796053; c=relaxed/simple; bh=qJMqcAIymKY6n3c9h31LaBmsTHKBdvHn1kIFG179Svc=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=trf7e+egW1tfsnqq0mqBwCOEZKrSP1yzboxwgtUftSNP4EBqn7a9pJHISsafudNqgUvx3WBudcB+YZ+M77p3XSS9zHWbhBBu9Q47Gdg60w6EhCBUYYqn8bTvsTVG3sPIIZyfWNF6AuZLIPEPCJxX6sdCQmeK1LWmZz5sKhCQy98= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1697796049; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=4cgwK2KeW6Z80EisHP2lDVcOwke9sJqqzZqSNxjozmQ=; b=N0zNuqobtO6shm9RO2E/lRw+5SPKTiTg2xGiFaxphQjpYpKT/1ZE6EK14NdTu5ZddiFqUB Ee8MnsWele7tNYjZvgDBagMphNyvBeDNTxhQ1X/26J9apvpSaoS14vgSzR9HlxfLgKGOiZ N9w4ekHVFL9fklPUjkt954Y1op5fgY8= Received: from mail-qk1-f198.google.com (mail-qk1-f198.google.com [209.85.222.198]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-204-L2pf7p30O2i4QO0q9RLCSg-1; Fri, 20 Oct 2023 06:00:47 -0400 X-MC-Unique: L2pf7p30O2i4QO0q9RLCSg-1 Received: by mail-qk1-f198.google.com with SMTP id af79cd13be357-7789577b582so71140785a.0 for ; Fri, 20 Oct 2023 03:00:47 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1697796047; x=1698400847; h=mime-version:user-agent:message-id:in-reply-to:date:references :organization:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=4cgwK2KeW6Z80EisHP2lDVcOwke9sJqqzZqSNxjozmQ=; b=LHb/IE60T+2Q3ejwpgZU2TNakU+lxjcTHDZ2HMBaonnozw8G8Te1PEdizO4oOLtX1R J11Q9P0tLM9MJ0G7bVV703sxTmv4BBXY6NTGnMTpRyy8wpG1B0o+FmvmAX+5i/jJyATj uFeLlK5EWi7V70QNx2XxRmRi5y8m2lyp3UT/KS9UlOpqlOubIKnSzT2PBWR3B5gRISYn 1hbkZGLzkpW7hZ8ibQFGEyoJepxggJloqvreQHC+nOGe3N0XYHWIRCUoAJnx8pvJS37S Q0CRu3Go1rmCfU6/XcmhEiu0xGsn3BW/HXDJK9NrRqWlcxkYvEnqYwX+PBL3+vy8FO+3 Q83g== X-Gm-Message-State: AOJu0Yzyo+DEir3j3XbTvZW9Ui4QtuSO7n0zxkgR7B7TT1nkbgUCzNTk p38tixfcRCC72XLXDH/vywgg9VfKaIEvtkv/7Up46beCs/05ig1mQwubFtfaRxiGk+b3W/ZcM3a POE0zUTqsOjFCySNCM3EjCni7jGd9 X-Received: by 2002:a05:620a:2714:b0:774:1e10:6822 with SMTP id b20-20020a05620a271400b007741e106822mr1192852qkp.77.1697796046552; Fri, 20 Oct 2023 03:00:46 -0700 (PDT) X-Google-Smtp-Source: AGHT+IGOgtzgi+QWrBg7WRxX1ezlVbHV6HN5WHt2nVjdvtxdtk+HWOaViPvUskeN5iNcqn0U96KFiA== X-Received: by 2002:a05:620a:2714:b0:774:1e10:6822 with SMTP id b20-20020a05620a271400b007741e106822mr1192833qkp.77.1697796046167; Fri, 20 Oct 2023 03:00:46 -0700 (PDT) Received: from localhost ([88.120.130.27]) by smtp.gmail.com with ESMTPSA id v10-20020ae9e30a000000b00777611164c6sm484280qkf.15.2023.10.20.03.00.44 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 20 Oct 2023 03:00:45 -0700 (PDT) Received: by localhost (Postfix, from userid 1000) id 66E2E5077C49; Fri, 20 Oct 2023 12:00:43 +0200 (CEST) From: Dodji Seketeli To: Dodji Seketeli Cc: John Moon , Trilok Soni , Satya Durga Srinivasu Prabhala , libabigail@sourceware.org Subject: [PATCH 4/7, applied] ir, comparison: Represent changed anonymous enums Organization: Red Hat / France References: <87cyx97h4j.fsf@redhat.com> X-Operating-System: AlmaLinux 9.2 X-URL: http://www.redhat.com Date: Fri, 20 Oct 2023 12:00:43 +0200 In-Reply-To: <87cyx97h4j.fsf@redhat.com> (Dodji Seketeli's message of "Fri, 20 Oct 2023 11:55:24 +0200") Message-ID: <87v8b162b8.fsf@redhat.com> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/27.2 (gnu/linux) MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-11.9 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: libabigail@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Mailing list of the Libabigail project List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libabigail-bounces+patchwork=sourceware.org@sourceware.org Hello, Now that added/removed non-reachable anonymous enums is supported, we want to represent changing an anonymous enum. Strictly speaking, adding or removing an enumerator from an anonymous enum is represented as deleting of the anonymous enum in the old state and the addition of an anonymous enum in the new state. This patch analyses the added/removed anonymous enums and if the old enum has enumerators contained in the new one, then it assumes we are looking at anonymous enum change. * include/abg-ir.h (is_enumerator_present_in_enum): Declare new public function. * src/abg-ir.cc (is_enumerator_present_in_enum): Turn this static function into a public one. * src/abg-comparison.cc (corpus_diff::priv::ensure_lookup_tables_populated): Detect that an removed/added anonymous enum is actually a changed anonymous enum and represent it as such. * tests/data/test-abidiff-exit/test-anonymous-enums-change-report-v{0,1}.txt: New reference test output files. * tests/data/test-abidiff-exit/test-anonymous-enums-change-v{0,1}.c: Source code for the some input test binary. * tests/data/test-abidiff-exit/test-anonymous-enums-change-v{0,1}.o: New test input binaries. * tests/data/Makefile.am: Add the new test materials above to source distribution. * tests/test-abidiff-exit.cc (in_out_specs): Add the new test input to the harness. Signed-off-by: Dodji Seketeli Applied to master. --- include/abg-ir.h | 4 + src/abg-comparison.cc | 76 ++++++++++++++++++ src/abg-ir.cc | 2 +- tests/data/Makefile.am | 6 ++ .../test-anonymous-enums-change-report-v0.txt | 16 ++++ .../test-anonymous-enums-change-report-v1.txt | 21 +++++ .../test-anonymous-enums-change-v0.c | 36 +++++++++ .../test-anonymous-enums-change-v0.o | Bin 0 -> 3296 bytes .../test-anonymous-enums-change-v1.c | 41 ++++++++++ .../test-anonymous-enums-change-v1.o | Bin 0 -> 3336 bytes tests/test-abidiff-exit.cc | 32 ++++++++ 11 files changed, 233 insertions(+), 1 deletion(-) create mode 100644 tests/data/test-abidiff-exit/test-anonymous-enums-change-report-v0.txt create mode 100644 tests/data/test-abidiff-exit/test-anonymous-enums-change-report-v1.txt create mode 100644 tests/data/test-abidiff-exit/test-anonymous-enums-change-v0.c create mode 100644 tests/data/test-abidiff-exit/test-anonymous-enums-change-v0.o create mode 100644 tests/data/test-abidiff-exit/test-anonymous-enums-change-v1.c create mode 100644 tests/data/test-abidiff-exit/test-anonymous-enums-change-v1.o new file mode 100644 index 00000000..886dbd2b index fb3d6fff..4c1eaea5 100644 diff --git a/include/abg-ir.h b/include/abg-ir.h index 3729e84a..b599ef3e 100644 --- a/include/abg-ir.h +++ b/include/abg-ir.h @@ -2813,6 +2813,10 @@ public: set_enum_type(enum_type_decl*); }; // end class enum_type_def::enumerator +bool +is_enumerator_present_in_enum(const enum_type_decl::enumerator &enr, + const enum_type_decl &enom); + bool equals(const typedef_decl&, const typedef_decl&, change_kind*); diff --git a/src/abg-comparison.cc b/src/abg-comparison.cc index 61fe89e0..1126ed58 100644 --- a/src/abg-comparison.cc +++ b/src/abg-comparison.cc @@ -14,6 +14,7 @@ #include #include #include +#include #include "abg-comparison-priv.h" #include "abg-reporter-priv.h" @@ -9619,6 +9620,81 @@ corpus_diff::priv::ensure_lookup_tables_populated() added_unreachable_types_[repr] = t; } } + + // Handle anonymous enums that got changed. An anonymous enum is + // designated by its flat textual representation. So a change to + // any of its enumerators results in a different enum. That is + // represented by a deletion of the previous anonymous enum, and + // the addition of a new one. For the user however, it's the same + // enum that changed. Let's massage this "added/removed" pattern + // to show what the user expects, namely, a changed anonymous + // enum. + { + std::set deleted_anon_enums; + std::set added_anon_enums; + + for (auto entry : deleted_unreachable_types_) + if (is_enum_type(entry.second) + && is_enum_type(entry.second)->get_is_anonymous()) + deleted_anon_enums.insert(is_enum_type(entry.second)); + + for (auto entry : added_unreachable_types_) + if (is_enum_type(entry.second) + && is_enum_type(entry.second)->get_is_anonymous()) + added_anon_enums.insert(is_enum_type(entry.second)); + + string_type_base_sptr_map added_anon_enum_to_erase; + string_type_base_sptr_map removed_anon_enum_to_erase; + + // Look for deleted anonymous enums which have enumerators + // present in an added anonymous enums ... + for (auto deleted_enum : deleted_anon_enums) + { + // Look for any enumerator of 'deleted_enum' that is also + // present in an added anonymous enum. + for (auto enr : deleted_enum->get_enumerators()) + { + bool this_enum_got_changed = false; + for (auto added_enum : added_anon_enums) + { + if (is_enumerator_present_in_enum(enr, *added_enum)) + { + // So the enumerator 'enr' from the + // 'deleted_enum' enum is also present in the + // 'added_enum' enum so we assume that + // 'deleted_enum' and 'added_enum' are the same + // enum that got changed. Let's represent it + // using a diff node. + diff_sptr d = compute_diff(deleted_enum, + added_enum, ctxt); + ABG_ASSERT(d->has_changes()); + string repr = + abigail::ir::get_pretty_representation(is_type(deleted_enum), + /*internal=*/false); + changed_unreachable_types_[repr]= d; + this_enum_got_changed = true; + string r1 = abigail::ir::get_pretty_representation(is_type(deleted_enum)); + string r2 = abigail::ir::get_pretty_representation(is_type(added_enum)); + removed_anon_enum_to_erase[r1] = deleted_enum; + added_anon_enum_to_erase[r2] = added_enum; + break; + } + } + if (this_enum_got_changed) + break; + } + } + + // Now remove the added/removed anonymous enums from their maps, + // as they are now represented as a changed enum, not an added + // and removed enum. + + for (auto entry : added_anon_enum_to_erase) + added_unreachable_types_.erase(entry.first); + + for (auto entry : removed_anon_enum_to_erase) + deleted_unreachable_types_.erase(entry.first); + } } } diff --git a/src/abg-ir.cc b/src/abg-ir.cc index 06dfbe20..171267f8 100644 --- a/src/abg-ir.cc +++ b/src/abg-ir.cc @@ -18997,7 +18997,7 @@ enum_has_non_name_change(const enum_type_decl& l, /// /// @return true iff the enumerator @p enr is present in the enum @p /// enom. -static bool +bool is_enumerator_present_in_enum(const enum_type_decl::enumerator &enr, const enum_type_decl &enom) { diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 1aa37b16..adb6d8bd 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -356,6 +356,12 @@ test-abidiff-exit/test-enumerator-changes1-v0.c \ test-abidiff-exit/test-enumerator-changes1-v0.o \ test-abidiff-exit/test-enumerator-changes1-v1.c \ test-abidiff-exit/test-enumerator-changes1-v1.o \ +test-abidiff-exit/test-anonymous-enums-change-report-v0.txt \ +test-abidiff-exit/test-anonymous-enums-change-report-v1.txt \ +test-abidiff-exit/test-anonymous-enums-change-v0.c \ +test-abidiff-exit/test-anonymous-enums-change-v0.o \ +test-abidiff-exit/test-anonymous-enums-change-v1.c \ +test-abidiff-exit/test-anonymous-enums-change-v1.o \ \ test-diff-dwarf/test0-v0.cc \ test-diff-dwarf/test0-v0.o \ diff --git a/tests/data/test-abidiff-exit/test-anonymous-enums-change-report-v0.txt b/tests/data/test-abidiff-exit/test-anonymous-enums-change-report-v0.txt new file mode 100644 index 00000000..2a408ea0 --- /dev/null +++ b/tests/data/test-abidiff-exit/test-anonymous-enums-change-report-v0.txt @@ -0,0 +1,16 @@ +Functions changes summary: 0 Removed, 0 Changed, 0 Added function +Variables changes summary: 0 Removed, 0 Changed, 0 Added variable +Unreachable types summary: 0 removed, 1 changed (1 filtered out), 1 added types + +1 changed type unreachable from any public interface: + + [C] 'enum {E4_0=0, E4_1=1, E4_2=2, E4_LAST_ELEMENT=3, }' changed: + type size hasn't changed + 2 enumerator deletions: + 'E4_2' value '2' + 'E4_LAST_ELEMENT' value '3' + +1 added type unreachable from any public interface: + + [A] 'enum {E5_0=0, E5_1=1, }' at test-anonymous-enums-change-v1.c:26:1 + diff --git a/tests/data/test-abidiff-exit/test-anonymous-enums-change-report-v1.txt b/tests/data/test-abidiff-exit/test-anonymous-enums-change-report-v1.txt new file mode 100644 index 00000000..8639fa44 --- /dev/null +++ b/tests/data/test-abidiff-exit/test-anonymous-enums-change-report-v1.txt @@ -0,0 +1,21 @@ +Functions changes summary: 0 Removed, 0 Changed, 0 Added function +Variables changes summary: 0 Removed, 0 Changed, 0 Added variable +Unreachable types summary: 0 removed, 2 changed, 1 added types + +2 changed types unreachable from any public interface: + + [C] 'enum {E4_0=0, E4_1=1, E4_2=2, E4_LAST_ELEMENT=3, }' changed: + type size hasn't changed + 2 enumerator deletions: + 'E4_2' value '2' + 'E4_LAST_ELEMENT' value '3' + + [C] 'enum {E3_0=0, E3_1=1, }' changed: + type size hasn't changed + 1 enumerator insertion: + 'E3_2' value '2' + +1 added type unreachable from any public interface: + + [A] 'enum {E5_0=0, E5_1=1, }' at test-anonymous-enums-change-v1.c:26:1 + diff --git a/tests/data/test-abidiff-exit/test-anonymous-enums-change-v0.c b/tests/data/test-abidiff-exit/test-anonymous-enums-change-v0.c new file mode 100644 index 00000000..443eb971 --- /dev/null +++ b/tests/data/test-abidiff-exit/test-anonymous-enums-change-v0.c @@ -0,0 +1,36 @@ +/* + * Compile this with: + * gcc -c -g -fno-eliminate-unused-debug-types test-anonymous-enums-change-v0.c + */ + +enum + { + E1_0, + E1_1, + } v1; + +enum + { + E3_0, + E3_1, + }; + +enum + { + E4_0, + E4_1, + E4_2, + E4_LAST_ELEMENT + }; + +enum + { + E0_0, + E0_1, + } v0; + +enum + { + E2_0, + E2_1, + } v2; diff --git a/tests/data/test-abidiff-exit/test-anonymous-enums-change-v0.o b/tests/data/test-abidiff-exit/test-anonymous-enums-change-v0.o new file mode 100644 index 0000000000000000000000000000000000000000..26c6df80f0d5203c520890f433cd5bba20b0bc4f GIT binary patch literal 3296 zcmb_e&2Jl35P$2nNn4Y&sU^Nrl6BRJ1laZ3aYJd9U=*3O5Gn*kLR_4+{p>8*>)>6R zL})`WoOXA z9GAtpMG(%Ks3NI=*$zm@0u)^MSc8>IMy+NXx$uSN0ySK*h=E#-;?L8b6>;U;W1hlT z7UtZgJnSoLY!qkm#q90EjiPZg|7_7T3h)M1VV)r%cf0W1qnSs9$j%#A=L&gqX<_NY z!*DuRL_WWfH|HSrC3rIM2$48WrN>JJSPpDY0yhlfuen)d_=fO+utcxClsO-#QDLJX z^t*v9`YNicpl;oH-P$Za zW0lKJ)hSyQx3cQ4x!0`aS0Vc?FCv3oS+VSXGz#Q;R|azEw?OrVt=;wg>udH})gD(Y zyKC8m!VawIQNx*ap>x6r!UwTS#kgGG^?3CtiTC0+xk@CfjS52zX3iL|G7!g~137t0yhFtCG+u-U>0-0~3((?n!Ay9%vgd0k44f=fvKRr$`h@=Cv%nl>3#c#$N{%?E%!tiYQ zJUL5^1&yO`lCVDz(6DQ5_$R=I_v)m0eKJkv1m(oVfJU+fFa zS(|vo!VWdnpA>+-hkiT*;D3msby)SBXjmlh73Fob=)P#zG4u`yv`|Tq?)wth()Rxb z{WS1CasLSE`)>oL=LAT6N+8b*#E^8~24HFR_o)6Y6&2rIoMU?ZJ<92lAG1R$bjSd4 zuFw2Ez){xYKgz@be^4AVj${4_7}DbJQA6@PKPevPBIjZsH3f1ireO;)}B{0Z^j)A##w5*Z76{6Aj89Torp literal 0 HcmV?d00001 diff --git a/tests/data/test-abidiff-exit/test-anonymous-enums-change-v1.c b/tests/data/test-abidiff-exit/test-anonymous-enums-change-v1.c --- /dev/null +++ b/tests/data/test-abidiff-exit/test-anonymous-enums-change-v1.c @@ -0,0 +1,41 @@ +/* + * Compile this with: + * gcc -c -g -fno-eliminate-unused-debug-types test-anonymous-enums-change-v1.c + */ + +enum + { + E1_0, + E1_1, + } v1; + +enum + { + E3_0, + E3_1, + E3_2 + }; + +enum + { + E4_0, + E4_1, + }; + +enum + { + E5_0, + E5_1, + }; + +enum + { + E0_0, + E0_1, + } v0; + +enum + { + E2_0, + E2_1, + } v2; diff --git a/tests/data/test-abidiff-exit/test-anonymous-enums-change-v1.o b/tests/data/test-abidiff-exit/test-anonymous-enums-change-v1.o new file mode 100644 index 0000000000000000000000000000000000000000..7d0f71b47a47d0a7eb893ddd41c4515cfb4b28fa GIT binary patch literal 3336 zcmb_eO>7%g5T5ngq^(JsG>D(JkTtX=q3o``aY6}7YAFqA1*#Ad7cORPKRXNdI`Xbf zB7|Cr3j&D?91s^SaN>jnhe`->=$%u!aOsH)2gHd32bg&~FWEeAFhY#9Z)U#vdprBy z*tfP`elcelpvZs?IMfsh@cxNIxhBOLoPjy`73BgL(wZmyacSfww}QeHElEzf8c@hj zk%Kv^z^NxtIBAkfBm<_qD5V3WoJ))ky{8!y`0Qtyu=TYLo1-#3Mb28$OFxf$da!i& zh$k3}V9uN`;zskvMro>8%HNn@D;X~opD&rlJibyD%o7Y0Zp>eOEcYlte$Kc!GhZ|p z&MnM7g6+Bh#o|WMoWa!R@yX!>km|40lf^?;K-)9O4a4|zW||oF10NI?lGYdtxzkA+ z88!@~pc9Ig6@)QtRqAD0DzH^1X(d^tic6K$Dp{9FT23Snwr{>>ZC0MMDix>dR4lLT zEtglzE7szxnD@FLv%&V3EV~yEL$Tfwp%?^BRQ*A7XMOL=s=ZpZN1kPOEV~`{ZP5*S zLFmWA9)`n6wCt8>3_Etbzbhix@~FIrF&Rjgtw}W8jmB%%$E-y~#z!m9F?ABMGv!}@Dl51Q@E-{&{7Jg{HmU-;i}#h4QCa$ zS)lW!auD=7<7)pj^NHS&YqEE$yT*E3!>_XbwuWD3{ap<&v;L8W(?BKqBw?~=R9E%q zOAW_)Ea@u^zr^}?%;|m8r|U<}{~OltY4{!1e_>8}PP0r*odN1wA%)ZVBvRk^twe=q znJfHT)+-wR6YJna`@Pt2Add$L-%(O5_F`~oG=kHJB5($x>pOBN*MqR#M;i_3y5DFF z#OR3GAJ8~H8qy6yp+c~P5(%zLz1i>e1pXp9VLuiQ4mvxE{ib>|wc-E9&tVw#R`pY5 zsVPC@1eaWt4