From patchwork Sat Nov 4 00:47:57 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: John Moon X-Patchwork-Id: 79054 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 5C5C53858284 for ; Sat, 4 Nov 2023 00:48:27 +0000 (GMT) X-Original-To: libabigail@sourceware.org Delivered-To: libabigail@sourceware.org Received: from mx0a-0031df01.pphosted.com (mx0a-0031df01.pphosted.com [205.220.168.131]) by sourceware.org (Postfix) with ESMTPS id BA5703858C66 for ; Sat, 4 Nov 2023 00:48:18 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org BA5703858C66 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=quicinc.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=quicinc.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org BA5703858C66 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=205.220.168.131 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1699058903; cv=none; b=cRNV9/l4sj3kszq1z+3i7MIHdwkuWDrJNf3lrGNLmBo5FmPAgZA+fTCDLQR4XsH94Sq5jIW9hMfsNPXWiPIsM6C2GlJeWSHWzUnIkRKzxs5HOnAKwvDaIfD0eQKfg2BvQ9WAp5VqYnvy8iIMS+6TF6X0qn3bThGr9h/vWe/H80I= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1699058903; c=relaxed/simple; bh=VOsIsBHrmpKVoTAt6MMnah8eYBuLQmvJqazPiXl3748=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=fjHw4+FmRzfntPhOt4whTXfd101U5JKepuqJ0hjkKvUBYI99KAxFhia9CJ2SWfk74FXw2PvprSB52e4osqg/boeetWj9VJnZLJT6Pe3F0AtXkSLONVdf+qxgMTl7GmRpyx3S6j+hQU51uPBbRNeOQkuzcKIyMAz9nhOTRQSVzfg= ARC-Authentication-Results: i=1; server2.sourceware.org Received: from pps.filterd (m0279865.ppops.net [127.0.0.1]) by mx0a-0031df01.pphosted.com (8.17.1.19/8.17.1.19) with ESMTP id 3A40iIss029240; Sat, 4 Nov 2023 00:48:14 GMT DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=quicinc.com; h=from : to : cc : subject : date : message-id : mime-version : content-type; s=qcppdkim1; bh=FS58Hv6grUzDBy+v6oOPkDRJa9RZgJFk1zBOr4qUKvo=; b=TDtOLZ0YIAht+1ciLphjMA8HDRqBt4wDJfjR5AhoBXiw9yDCWj647XN88V5EWw3VukKd KpTr+VzqpI96lN+//Gn0gG05TOKxmSyueP7AR495V6JDxr0jnYW3Ng65YPP6L70U2M6z hNoDkNQVBmbXXRpvqMfwOxAhFG18Yk32iVeDwJvIsPdwDYyhgXAHilVKVV6dMjh8C1JU PgTveVhD80YUJ+Oa3J0oZh5SXqopis/S3/WQy7vVC0b5ppldfkY+0wSgQE9ec6urZjJ2 xde6aaDss9C4urnUo0Y1zWdfyoQ1IQGgK1fhbYQ2zj14UkKWi30DHg7PZmKJuuhGfLOG GQ== Received: from nalasppmta01.qualcomm.com (Global_NAT1.qualcomm.com [129.46.96.20]) by mx0a-0031df01.pphosted.com (PPS) with ESMTPS id 3u4yk0smmt-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Sat, 04 Nov 2023 00:48:14 +0000 Received: from nalasex01b.na.qualcomm.com (nalasex01b.na.qualcomm.com [10.47.209.197]) by NALASPPMTA01.qualcomm.com (8.17.1.5/8.17.1.5) with ESMTPS id 3A40mDr7019471 (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Sat, 4 Nov 2023 00:48:13 GMT Received: from hu-johmoo-lv.qualcomm.com (10.49.16.6) by nalasex01b.na.qualcomm.com (10.47.209.197) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.1118.39; Fri, 3 Nov 2023 17:48:13 -0700 From: John Moon To: , CC: John Moon Subject: [PATCH] suppression: Add "has_strict_flexible_array_data_member_conversion" property Date: Fri, 3 Nov 2023 17:47:57 -0700 Message-ID: <20231104004757.21305-1-quic_johmoo@quicinc.com> X-Mailer: git-send-email 2.17.1 MIME-Version: 1.0 X-Originating-IP: [10.49.16.6] X-ClientProxiedBy: nalasex01c.na.qualcomm.com (10.47.97.35) To nalasex01b.na.qualcomm.com (10.47.209.197) X-QCInternal: smtphost X-Proofpoint-Virus-Version: vendor=nai engine=6200 definitions=5800 signatures=585085 X-Proofpoint-ORIG-GUID: btBPhuzMUyW_AKVj3mzRm_CA65PqiI9s X-Proofpoint-GUID: btBPhuzMUyW_AKVj3mzRm_CA65PqiI9s X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.272,Aquarius:18.0.987,Hydra:6.0.619,FMLib:17.11.176.26 definitions=2023-11-03_22,2023-11-02_03,2023-05-22_02 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 clxscore=1015 priorityscore=1501 mlxscore=0 spamscore=0 malwarescore=0 suspectscore=0 impostorscore=0 mlxlogscore=999 bulkscore=0 lowpriorityscore=0 adultscore=0 phishscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.12.0-2310240000 definitions=main-2311040005 X-Spam-Status: No, score=-11.2 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, RCVD_IN_DNSWL_LOW, SPF_HELO_NONE, SPF_PASS, TXREP, T_SCC_BODY_TEXT_LINE 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 From: Dodji Seketeli In the past, it was common to have a "fake flex array" at the end of a structure. Like this: Nowadays, with improved compiler support, it's more common to use a real flex array. As this is a common change which changes ABI representation in a compatible way, we should have a suppression for it. For example, if you have a change like this: struct foo { int x; int flex[1]; }; ... struct foo { int x; int flex[]; }; abidiff reports: [C] 'struct foo' changed: type size changed from 64 to 32 (in bits) 1 data member change: type of 'int flex[1]' changed: type name changed from 'int[1]' to 'int[]' array type size changed from 32 to 'unknown' array type subrange 1 changed length from 1 to 'unknown' With a new has_strict_flexible_array_data_member_conversion property, users can specify a suppression which stops abidiff from emitting this diff for any "fake" flex arrays being converted to real ones: [suppress_type] type_kind = struct has_size_change = true has_strict_flexible_array_data_member_conversion = true * include/abg-fwd.h (ir::has_fake_flexible_array_data_member): Declare new accessor functions. * include/abg-suppression.h (type_suppression::{,set_}has_strict_fam_conversion): Declare new accessor functions. * src/abg-ir.cc (ir::has_fake_flexible_array_data_member): Define new accessor functions. * src/abg-suppression-priv.h (type_suppression::priv::has_strict_fam_conv_): Define new data member. * src/abg-suppression.cc (type_suppression::{,set_}has_strict_fam_conversion): Define new accessor functions. (type_suppression::suppresses_diff): For a type suppression to match a fake flex array conversion, has_size_change must be true and it must change from a fake flex array to a real flex array. (read_type_suppression): Parse the new 'has_strict_flexible_array_data_member_conversion' property to set the type_suppression::set_has_strict_fam_conversion property. * doc/manuals/libabigail-concepts.rst: Add an entry for the new 'has_strict_flexible_array_data_member_conversion' property. * tests/data/test-diff-suppr/test-has-strict-flexible-array-data-member-conversion-{1,2}.suppr: Add new test suppression files. * tests/data/test-diff-suppr/test-has-strict-flexible-array-data-member-conversion-report-{1,2}.txt: Add new test reference output files. * tests/data/test-diff-suppr/test-has-strict-flexible-array-data-member-conversion-v{0,1}.c: Add source code for new binary test input files. * tests/data/test-diff-suppr/test-has-strict-flexible-array-data-member-conversion-v{0,1}.o: Add new binary test input files. * tests/data/Makefile.am: Add the new test files to the source distribution. * tests/test-diff-suppr.cc (in_out_specs): Add the new test input files to this test harness. Signed-off-by: Dodji Seketeli Signed-off-by: John Moon Signed-off-by: Dodji Seketeli Signed-off-by: John Moon --- doc/manuals/libabigail-concepts.rst | 26 ++++++++- include/abg-fwd.h | 9 +++ include/abg-suppression.h | 6 ++ src/abg-ir.cc | 28 +++++++++ src/abg-suppression-priv.h | 6 +- src/abg-suppression.cc | 53 ++++++++++++++++-- ...xible-array-data-member-conversion-1.suppr | 4 ++ ...xible-array-data-member-conversion-2.suppr | 3 + ...-array-data-member-conversion-report-1.txt | 4 ++ ...-array-data-member-conversion-report-2.txt | 14 +++++ ...flexible-array-data-member-conversion-v0.c | 11 ++++ ...flexible-array-data-member-conversion-v0.o | Bin 0 -> 2440 bytes ...flexible-array-data-member-conversion-v1.c | 11 ++++ ...flexible-array-data-member-conversion-v1.o | Bin 0 -> 2432 bytes tests/test-diff-suppr.cc | 20 +++++++ 15 files changed, 186 insertions(+), 9 deletions(-) create mode 100644 tests/data/test-diff-suppr/test-has-strict-flexible-array-data-member-conversion-1.suppr create mode 100644 tests/data/test-diff-suppr/test-has-strict-flexible-array-data-member-conversion-2.suppr create mode 100644 tests/data/test-diff-suppr/test-has-strict-flexible-array-data-member-conversion-report-1.txt create mode 100644 tests/data/test-diff-suppr/test-has-strict-flexible-array-data-member-conversion-report-2.txt create mode 100644 tests/data/test-diff-suppr/test-has-strict-flexible-array-data-member-conversion-v0.c create mode 100644 tests/data/test-diff-suppr/test-has-strict-flexible-array-data-member-conversion-v0.o create mode 100644 tests/data/test-diff-suppr/test-has-strict-flexible-array-data-member-conversion-v1.c create mode 100644 tests/data/test-diff-suppr/test-has-strict-flexible-array-data-member-conversion-v1.o new file mode 100644 index 00000000..95386d41 index 8c9ad070..119be55b 100644 diff --git a/doc/manuals/libabigail-concepts.rst b/doc/manuals/libabigail-concepts.rst index 28e71684..f9e27ff9 100644 --- a/doc/manuals/libabigail-concepts.rst +++ b/doc/manuals/libabigail-concepts.rst @@ -619,9 +619,28 @@ names start with the string "private_data_member". {72, end} } +.. _suppr_has_strict_flexible_array_data_member_conversion_label: - .. _suppr_has_size_change_property_label: +* ``has_strict_flexible_array_data_member_conversion`` + + Usage: + + ``has_strict_flexible_array_data_member_conversion`` ``=`` yes | no + + Suppresses change reports involving a type which has a "fake" + flexible array member at the end of the struct which is converted + to a real flexible array member. This would be a member like + ``data[1]`` being converted to ``data[]``. + + Please note: a conversion to a flex array like this + will *always* result in the structure changing size, + so the suppression will not take effect unless the + :ref:`has_size_change` + property is present and set to ``yes``. + +.. _suppr_has_size_change_property_label: + * ``has_size_change`` @@ -631,9 +650,10 @@ names start with the string "private_data_member". This property is to be used in conjunction with the properties -:ref:`has_data_member_inserted_between` +:ref:`has_data_member_inserted_between`, +:ref:`has_data_members_inserted_between`, and -:ref:`has_data_members_inserted_between`. +:ref:`has_strict_flexible_array_data_member_conversion` Those properties will not match a type change if the size of the type changes, unless the ``has_size_changes`` property is set to ``yes``. diff --git a/include/abg-fwd.h b/include/abg-fwd.h index 7d6637b9..de5b72b0 100644 --- a/include/abg-fwd.h +++ b/include/abg-fwd.h @@ -490,6 +490,15 @@ has_flexible_array_data_member(const class_decl*); var_decl_sptr has_flexible_array_data_member(const class_decl_sptr&); +var_decl_sptr +has_fake_flexible_array_data_member(const class_decl&); + +var_decl_sptr +has_fake_flexible_array_data_member(const class_decl*); + +var_decl_sptr +has_fake_flexible_array_data_member(const class_decl_sptr&); + bool is_declaration_only_class_or_union_type(const type_base *t, bool look_through_decl_only = false); diff --git a/include/abg-suppression.h b/include/abg-suppression.h index 996600bb..dd0870bc 100644 --- a/include/abg-suppression.h +++ b/include/abg-suppression.h @@ -336,6 +336,12 @@ public: void set_changed_enumerators_regexp(const vector&); + bool + has_strict_fam_conversion () const; + + void + set_has_strict_fam_conversion(bool); + virtual bool suppresses_diff(const diff* diff) const; diff --git a/src/abg-ir.cc b/src/abg-ir.cc index 4a652b0f..7ba7f72d 100644 --- a/src/abg-ir.cc +++ b/src/abg-ir.cc @@ -10822,6 +10822,34 @@ var_decl_sptr has_flexible_array_data_member(const class_decl_sptr& klass) {return has_flexible_array_data_member(klass.get());} +var_decl_sptr +has_fake_flexible_array_data_member(const class_decl& klass) +{ + var_decl_sptr nil; + const class_or_union::data_members& dms = klass.get_data_members(); + if (dms.empty()) + return nil; + + if (array_type_def_sptr array = is_array_type(dms.back()->get_type())) + {// The type of the last data member is an array. + if (array->get_subranges().size() == 1 + && array->get_subranges()[0]->get_length() == 1) + // The array has a size of one. We are thus looking at a + // "fake" flexible array data member. Let's return it. + return dms.back(); + } + + return nil; +} + +var_decl_sptr +has_fake_flexible_array_data_member(const class_decl* klass) +{return has_fake_flexible_array_data_member(*klass);} + +var_decl_sptr +has_fake_flexible_array_data_member(const class_decl_sptr& klass) +{return has_fake_flexible_array_data_member(klass.get());} + /// Test wheter a type is a declaration-only class. /// /// @param t the type to considier. diff --git a/src/abg-suppression-priv.h b/src/abg-suppression-priv.h index 351c5965..e4d65df8 100644 --- a/src/abg-suppression-priv.h +++ b/src/abg-suppression-priv.h @@ -586,6 +586,9 @@ class type_suppression::priv mutable regex::regex_t_sptr source_location_to_keep_regex_; mutable vector changed_enumerator_names_; mutable vector changed_enumerators_regexp_; + // Whether the "has_strict_flexible_array_data_member_conversion" + // property was set. + bool has_strict_fam_conv_; priv(); @@ -602,7 +605,8 @@ public: type_kind_(type_kind), consider_reach_kind_(consider_reach_kind), reach_kind_(reach_kind), - has_size_change_(false) + has_size_change_(false), + has_strict_fam_conv_(false) {} /// Get the regular expression object associated to the 'type_name_regex' diff --git a/src/abg-suppression.cc b/src/abg-suppression.cc index 326d003e..91199629 100644 --- a/src/abg-suppression.cc +++ b/src/abg-suppression.cc @@ -808,6 +808,14 @@ void type_suppression::set_changed_enumerators_regexp(const vector& n) {priv_->changed_enumerators_regexp_ = n;} +bool +type_suppression::has_strict_fam_conversion () const +{return priv_->has_strict_fam_conv_;} + +void +type_suppression::set_has_strict_fam_conversion(bool f) +{priv_->has_strict_fam_conv_ = f;} + /// Evaluate this suppression specification on a given diff node and /// say if the diff node should be suppressed or not. /// @@ -967,6 +975,11 @@ type_suppression::suppresses_diff(const diff* diff) const const class_diff* klass_diff = dynamic_cast(d); if (klass_diff) { + const class_decl_sptr& first_class = + klass_diff->first_class_decl(); + const class_decl_sptr& second_class = + klass_diff->second_class_decl(); + // We are looking at a class diff ... if (!get_data_member_insertion_ranges().empty()) { @@ -981,9 +994,6 @@ type_suppression::suppresses_diff(const diff* diff) const // that suppression applies to types that have size // change. - const class_decl_sptr& first_type_decl = - klass_diff->first_class_decl(); - if (klass_diff->inserted_data_members().empty() && klass_diff->changed_data_members().empty()) // So there is a has_data_member_inserted_* clause, @@ -1001,7 +1011,7 @@ type_suppression::suppresses_diff(const diff* diff) const for (const auto& range : get_data_member_insertion_ranges()) if (is_data_member_offset_in_range(is_var_decl(member), range, - first_type_decl.get())) + first_class.get())) matched = true; if (!matched) @@ -1017,7 +1027,7 @@ type_suppression::suppresses_diff(const diff* diff) const for (const auto& range : get_data_member_insertion_ranges()) if (is_data_member_offset_in_range(member, range, - first_type_decl.get())) + first_class.get())) matched = true; if (!matched) @@ -1027,6 +1037,28 @@ type_suppression::suppresses_diff(const diff* diff) const else return false; } + + // Support for the + // "has_strict_flexible_array_data_member_conversion = true" + // clause. + if (has_strict_fam_conversion()) + { + // Let's detect if the first class of the diff has a fake + // flexible array data member that got turned into a real + // flexible array data member. + if (!( + (get_has_size_change()) + // This situation will always result in a size change, + // so check that "has_size_change = true" before + // processing further + && + (has_fake_flexible_array_data_member(first_class) + && has_flexible_array_data_member(second_class)) + // A fake flexible array member has been changed into + // a real flexible array ... + )) + return false; + } } const enum_diff* enum_dif = dynamic_cast(d); @@ -2321,6 +2353,14 @@ read_type_suppression(const ini::config::section& section) } } + // Support "has_strict_flexible_array_data_member_conversion" + ini::simple_property_sptr has_strict_fam_conv = + is_simple_property + (section.find_property("has_strict_flexible_array_data_member_conversion")); + string has_strict_fam_conv_str = has_strict_fam_conv + ? has_strict_fam_conv->get_value()->as_string() + : ""; + if (section.get_name() == "suppress_type") result.reset(new type_suppression(label_str, name_regex_str, name_str)); else if (section.get_name() == "allow_type") @@ -2388,6 +2428,9 @@ read_type_suppression(const ini::config::section& section) && !changed_enumerators_regexp.empty()) result->set_changed_enumerators_regexp(changed_enumerators_regexp); + if (has_strict_fam_conv_str == "yes" || has_strict_fam_conv_str == "true") + result->set_has_strict_fam_conversion(true); + return result; } diff --git a/tests/data/test-diff-suppr/test-has-strict-flexible-array-data-member-conversion-1.suppr b/tests/data/test-diff-suppr/test-has-strict-flexible-array-data-member-conversion-1.suppr new file mode 100644 index 00000000..5cb8d880 --- /dev/null +++ b/tests/data/test-diff-suppr/test-has-strict-flexible-array-data-member-conversion-1.suppr @@ -0,0 +1,4 @@ +[suppress_type] + type_kind = struct + has_size_change = true + has_strict_flexible_array_data_member_conversion = true diff --git a/tests/data/test-diff-suppr/test-has-strict-flexible-array-data-member-conversion-2.suppr b/tests/data/test-diff-suppr/test-has-strict-flexible-array-data-member-conversion-2.suppr new file mode 100644 index 00000000..384409d0 --- /dev/null +++ b/tests/data/test-diff-suppr/test-has-strict-flexible-array-data-member-conversion-2.suppr @@ -0,0 +1,3 @@ +[suppress_type] + type_kind = struct + has_strict_flexible_array_data_member_conversion = true diff --git a/tests/data/test-diff-suppr/test-has-strict-flexible-array-data-member-conversion-report-1.txt b/tests/data/test-diff-suppr/test-has-strict-flexible-array-data-member-conversion-report-1.txt new file mode 100644 index 00000000..b4ea5bf1 --- /dev/null +++ b/tests/data/test-diff-suppr/test-has-strict-flexible-array-data-member-conversion-report-1.txt @@ -0,0 +1,4 @@ +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, 0 changed (1 filtered out), 0 added type + diff --git a/tests/data/test-diff-suppr/test-has-strict-flexible-array-data-member-conversion-report-2.txt b/tests/data/test-diff-suppr/test-has-strict-flexible-array-data-member-conversion-report-2.txt new file mode 100644 index 00000000..2352dd4e --- /dev/null +++ b/tests/data/test-diff-suppr/test-has-strict-flexible-array-data-member-conversion-report-2.txt @@ -0,0 +1,14 @@ +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, 0 added type + +1 changed type unreachable from any public interface: + + [C] 'struct foo' changed: + type size changed from 64 to 32 (in bits) + 1 data member change: + type of 'int flex[1]' changed: + type name changed from 'int[1]' to 'int[]' + array type size changed from 32 to 'unknown' + array type subrange 1 changed length from 1 to 'unknown' + diff --git a/tests/data/test-diff-suppr/test-has-strict-flexible-array-data-member-conversion-v0.c b/tests/data/test-diff-suppr/test-has-strict-flexible-array-data-member-conversion-v0.c new file mode 100644 index 00000000..1397cd52 --- /dev/null +++ b/tests/data/test-diff-suppr/test-has-strict-flexible-array-data-member-conversion-v0.c @@ -0,0 +1,11 @@ +/* + * Compile this with: + * gcc -g -c test-has-strict-flexible-array-data-member-conversion-v0.c + */ +struct foo +{ + int x; + int flex[1]; +}; + +struct foo S; diff --git a/tests/data/test-diff-suppr/test-has-strict-flexible-array-data-member-conversion-v0.o b/tests/data/test-diff-suppr/test-has-strict-flexible-array-data-member-conversion-v0.o new file mode 100644 index 0000000000000000000000000000000000000000..8d0a755d353d580e6afb96dc55a021322caf5a93 GIT binary patch literal 2440 zcmbtV!EVz)5FN)&<6;_GMF^^b* zJ;IGM=UzDT1$+hP{sNqkIFy<7Zt5&SNOUCc%)FV|ncbaTA3S{eD5q&4NrP25(Ig7+ zA%7y*xLAWJI0pgBV_Y|Q*jX-%A=eB;FtmIPb$t)C0tuHXTSsu*fE@IwG_~m$hj2%H#b`D;U=!#WnGU&c0CMsSrq$WVDDC( zI!{FIrB1j&7CTgzat^mfwcaPTX_rtlaTE*YjJh-zn3| z<1#Ifi+00rHto3I>qP+TYirBa{Fc`bl73p7eY?-K>bvTaQ(3s4q9Zmt15wkB!S~|~ zG!XTXos2yqPIBNV1RX7-NEaQh+H@FKA+GDdoA(c!4ikT11n6+)FZj@H8u;jd-Kx-i z)TiOcvFuLCPt!P?IHz18RVZajEh{6OR#n4~XgXzlMTE;|IYdHL9%A5Q`SN_IPrcraXsWol=8Ne61JCsLtP7w7sud4tm8Tj=Ji|K ze$Wij#%0&@BDOnZb|W`vF$_sPI)1>UjI)DN54&B4SH}tPb~$)6@pj>D;}xB1n8j#X zyIilFtR@A8lW&&hP$5jc0ky1CMpommp^eV(f8(ndtIkR3YBV6>gE2&^?@hGQf2cO( z1z@E=C-^GxN#q2{UZc02XazrYPQt&?fV7*k!KnH^M~jM)^Sl5pQZl6aK0({4`j15Y zvQDa>UKh3gZN$_$5rW48d`(J*R9|XA!YUab5)XyrHwBmRlGFZ-ir-IvnsDPE@dqMa zaf3qjS+$-M7SY z-6&p&0wD`Rqhkd|5c*CdG+JG@=h$6lSiW!V8%-;;j2`RR%r_dIJ7j*~c&;(5n2jhA zxx@OiZGE*;(I1%i%>~`)g#(u@x0%auM^r7p(OKTBEgFke-DvAZD+sN|uF?0skTpWj zr_`R?hAuApzzv+X%bGe}`#Fj$4+8&g`JLD4d0zR0=kEr6tHH`$$F^*zZ8_aCZ61{A zfRSi4omR^T2K~Mdu)eywq~F}O2W~isk7e8&L|WxzrDiTvZ^dZJWycUTTNZvEr#Yyp z9gNMvKcXC?q9u(~-r2HEg)te5bQPkmJ!4a0{0v5b3K#x@pPHhPj|$k*5sn@$*@O0uSK%tov1dj~Dl`}L7WEKKu( z_{Q-GA zagkTxq^G%x_!