From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: 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 C689B385841B for ; Fri, 4 Nov 2022 14:40:49 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org C689B385841B Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1667572849; 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: content-transfer-encoding:content-transfer-encoding; bh=y8wNo+z7Lw4JQraL7AJVDg+8EN59fDbprSunWR8Y4WI=; b=IGYCBG2zCOUc3IU+LDsAoHpGOikA/MCIXUO98B+OsEEyhWAVnMdB8+dKtnXdi4ZpdaSBwn muXlZiDHOh93z7iV9ULBf/bbnO2IOeof8xhy1AZVc/7YWZ10RaYsNEUftnY77dkZRo2On/ gaqlz1IagEk0ID6TJp5UM0izhm338Ag= Received: from mail-qv1-f71.google.com (mail-qv1-f71.google.com [209.85.219.71]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_128_GCM_SHA256) id us-mta-664-f8GqhRegPPOloPjZUFEl-Q-1; Fri, 04 Nov 2022 10:40:39 -0400 X-MC-Unique: f8GqhRegPPOloPjZUFEl-Q-1 Received: by mail-qv1-f71.google.com with SMTP id mo15-20020a056214330f00b004b96d712bccso3375976qvb.22 for ; Fri, 04 Nov 2022 07:40:39 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=SphGuRr9dR7SuWUdyDmMqD0AjfmOVRyZmuX2vJ5fm3w=; b=5FbY4CTov4h8z044jOL6NGnF1OfYwtWlzlv530W37yDfietxNyiNwjg2froOh+q+wR xeF0VmmlV90OmXIFrQFVocQ2bFp6xUG24noUIazWSGRqwENV9Jbv10Mf9TkO+tpw9SZQ 6JsIBUhmWgJUtZkg1VNXvRcOBqnOh99cQARgQrbr+KmIOEceAKF/KcZjbS9DqHu9Dub7 xag+2QB9Vcj8SpjRLJ140vIQ10PMWYU2pkwfIyypdIlBqhi23vejEOAVB+/+IkKXafqt kaW1mEyCax0MAVypCy1FC3cwZ9wjLX86Uf1bjiO9+KV0t34/CKi36OZcrb54ARKRnHVz JtYA== X-Gm-Message-State: ACrzQf2k5Ez3Jr6keVnKe4SblrOxT2NEC4hw7LXaTh6LjXG/IxEwnEUC C0KcOkgpsN0xAyQcGtAI4Wno4WLHRHJcaulhoTUxR9GxAa/vCAfsvbNrDrnVdNeA9TzPAuqNVpm lstvFhBs3e5KJng4iAh0OwufIELLUA2epJ3LjUMJIyVbDHDQDrACtZMC4MH1w2nrQtA== X-Received: by 2002:a05:6214:20c1:b0:4b9:f285:de7e with SMTP id 1-20020a05621420c100b004b9f285de7emr32114033qve.14.1667572835269; Fri, 04 Nov 2022 07:40:35 -0700 (PDT) X-Google-Smtp-Source: AMsMyM471iDhWRwDLvAtqGbYKKy+xuSqIyw7UMH59mS0b0ixK+oFIRjRxjY8ZOPlSsmHgHHNx8xgOg== X-Received: by 2002:a05:6214:20c1:b0:4b9:f285:de7e with SMTP id 1-20020a05621420c100b004b9f285de7emr32113765qve.14.1667572832073; Fri, 04 Nov 2022 07:40:32 -0700 (PDT) Received: from barrymore.redhat.com (130-44-159-43.s15913.c3-0.arl-cbr1.sbo-arl.ma.cable.rcncustomer.com. [130.44.159.43]) by smtp.gmail.com with ESMTPSA id y27-20020a37f61b000000b006fa5815b88dsm2958371qkj.88.2022.11.04.07.40.30 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 04 Nov 2022 07:40:30 -0700 (PDT) From: Jason Merrill To: gcc-patches@gcc.gnu.org Cc: Andrew Sutton , Andrew Marmaduke , Michael Lopez , Jeff Chapman II Subject: [PATCH RFC] c++: implement P1492 contracts Date: Fri, 4 Nov 2022 10:40:26 -0400 Message-Id: <20221104144026.2311096-1-jason@redhat.com> X-Mailer: git-send-email 2.31.1 MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: 8bit Content-Type: text/plain; charset="US-ASCII"; x-default=true X-Spam-Status: No, score=-25.2 required=5.0 tests=BAYES_00,DKIM_INVALID,DKIM_SIGNED,GIT_PATCH_0,KAM_DMARC_NONE,KAM_DMARC_STATUS,KAM_SHORT,RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H2,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 List-Id: The front-end parts of the experimental C++ contracts implementation. The contracts working group are keen to have this available in a released compiler, and I've cleaned it up to the point that I think it's ready. I'm planning to commit it after the library and input.cc bits are approved. -- >8 -- Implement the P1492 versions of contracts, along with extensions that support the emulation of N4820 and other proposals. This implementation assigns a concrete semantic (one of: ignore, assume, enforce, or observe) to each contract attribute depending on its labels and configuration options. Co-authored-by: Andrew Sutton Co-authored-by: Andrew Marmaduke Co-authored-by: Michael Lopez Co-authored-by: Jeff Chapman II gcc/ChangeLog: * doc/invoke.texi: Document contracts flags. gcc/c-family/ChangeLog: * c.opt: Add contracts flags. * c-cppbuiltin.cc (c_cpp_builtins): Add contracts feature-test macros. gcc/cp/ChangeLog: * cp-tree.h (enum cp_tree_index): Add CPTI_PSEUDO_CONTRACT_VIOLATION. (pseudo_contract_violation_type): New macro. (struct saved_scope): Add x_processing_contract_condition. (processing_contract_condition): New macro. (comparing_override_contracts): New variable decl. (find_contract): New inline. (set_decl_contracts): New inline. (get_contract_semantic): New inline. (set_contract_semantic): New inline. * constexpr.cc (cxx_eval_assert): Split out from... (cxx_eval_internal_function): ...here. (cxx_eval_constant_expression): Use it for contracts. (potential_constant_expression_1): Handle contracts. * cp-gimplify.cc (cp_genericize_r): Handle contracts. * cp-objcp-common.cc (cp_tree_size): Handle contracts. (cp_common_init_ts): Handle contracts. (cp_handle_option): Handle contracts. * decl.cc (duplicate_decls): Handle contracts. (check_tag_decl): Check for bogus contracts. (start_decl): Check flag_contracts. (grokfndecl): Call rebuild_postconditions. (grokdeclarator): Handle contract attributes. (start_preparsed_function): Call start_function_contracts. (finish_function): Call finish_function_contracts. * decl2.cc (cp_check_const_attributes): Skip contracts. (comdat_linkage): Handle outlined contracts. * error.cc (dump_type): Handle null TYPE_IDENTIFIER. * g++spec.cc (EXPERIMENTAL): New macro. (lang_specific_driver): Add -lstdc++exp if -fcontracts. * mangle.cc (write_encoding): Handle outlined contracts. * module.cc (trees_out::fn_parms_init): Handle outlined contracts. (trees_in::fn_parms_init): Likewise. (check_mergeable_decl): Likewise. (module_state_config::get_dialect): Record -fcontracts. * parser.h (struct cp_unparsed_functions_entry): Add contracts. * parser.cc (unparsed_contracts): New macro. (push_unparsed_function_queues): Adjust. (contract_attribute_p): New. (cp_parser_statement): Check contracts. (cp_parser_decl_specifier_seq): Handle contracts. (cp_parser_skip_to_closing_square_bracket): Split out... (cp_parser_skip_up_to_closing_square_bracket): ...this fn. (cp_parser_class_specifier): Do contract late parsing. (cp_parser_class_head): Check contracts. (cp_parser_contract_role): New. (cp_parser_contract_mode_opt): New. (find_error, contains_error_p): New. (cp_parser_contract_attribute_spec): New. (cp_parser_late_contract_condition): New. (cp_parser_std_attribute_spec): Handle contracts. (cp_parser_save_default_args): Also save contracts. * pt.cc (register_parameter_specializations): No longer static. (register_local_identity): New. (check_explicit_specialization): Call remove_contract_attributes. (tsubst_contract, tsubst_contract_attribute): New. (tsubst_contract_attributes): New. (tsubst_attribute): Add comment. (tsubst_copy): Also allow parm when processing_contract_condition. (tsubst_expr): Handle contracts. (regenerate_decl_from_template): Handle contracts. * search.cc (check_final_overrider): Compare contracts. * semantics.cc (set_cleanup_locs): Skip POSTCONDITION_STMT. (finish_non_static_data_member): Check contracts. (finish_this_expr): Check contracts. (process_outer_var_ref): Handle contracts. (finish_id_expression_1): Handle contracts. (apply_deduced_return_type): Adjust contracts. * tree.cc (handle_contract_attribute): New. (get_innermost_component, is_this_expression): New. (comparing_this_references): New. (equivalent_member_references): New. (cp_tree_equal): Check it. * typeck.cc (check_return_expr): Apply contracts. * Make-lang.in: Add contracts.o. * config-lang.in: Add contracts.cc. * cp-tree.def (ASSERTION_STMT, PRECONDITION_STMT) (POSTCONDITION_STMT): New. * contracts.h: New file. * contracts.cc: New file. gcc/testsuite/ChangeLog: * g++.dg/modules/modules.exp: Pass dg-options to link command. * lib/g++.exp: Add -L for libstdc++exp.a. * g++.dg/contracts/backtrace_handler/assert_fail.cpp: New test. * g++.dg/contracts/backtrace_handler/handle_contract_violation.cpp: New test. * g++.dg/contracts/contracts-access1.C: New test. * g++.dg/contracts/contracts-assume1.C: New test. * g++.dg/contracts/contracts-assume2.C: New test. * g++.dg/contracts/contracts-assume3.C: New test. * g++.dg/contracts/contracts-assume4.C: New test. * g++.dg/contracts/contracts-assume5.C: New test. * g++.dg/contracts/contracts-assume6.C: New test. * g++.dg/contracts/contracts-comdat1.C: New test. * g++.dg/contracts/contracts-config1.C: New test. * g++.dg/contracts/contracts-constexpr1.C: New test. * g++.dg/contracts/contracts-constexpr2.C: New test. * g++.dg/contracts/contracts-constexpr3.C: New test. * g++.dg/contracts/contracts-conversion1.C: New test. * g++.dg/contracts/contracts-ctor-dtor1.C: New test. * g++.dg/contracts/contracts-ctor-dtor2.C: New test. * g++.dg/contracts/contracts-cv1.C: New test. * g++.dg/contracts/contracts-deduced1.C: New test. * g++.dg/contracts/contracts-deduced2.C: New test. * g++.dg/contracts/contracts-friend1.C: New test. * g++.dg/contracts/contracts-ft1.C: New test. * g++.dg/contracts/contracts-ignore1.C: New test. * g++.dg/contracts/contracts-ignore2.C: New test. * g++.dg/contracts/contracts-large-return.C: New test. * g++.dg/contracts/contracts-multiline1.C: New test. * g++.dg/contracts/contracts-multiple-inheritance1.C: New test. * g++.dg/contracts/contracts-multiple-inheritance2.C: New test. * g++.dg/contracts/contracts-nested-class1.C: New test. * g++.dg/contracts/contracts-nested-class2.C: New test. * g++.dg/contracts/contracts-nocopy1.C: New test. * g++.dg/contracts/contracts-override.C: New test. * g++.dg/contracts/contracts-post1.C: New test. * g++.dg/contracts/contracts-post2.C: New test. * g++.dg/contracts/contracts-post3.C: New test. * g++.dg/contracts/contracts-post4.C: New test. * g++.dg/contracts/contracts-post5.C: New test. * g++.dg/contracts/contracts-post6.C: New test. * g++.dg/contracts/contracts-pre1.C: New test. * g++.dg/contracts/contracts-pre10.C: New test. * g++.dg/contracts/contracts-pre2.C: New test. * g++.dg/contracts/contracts-pre2a1.C: New test. * g++.dg/contracts/contracts-pre2a2.C: New test. * g++.dg/contracts/contracts-pre3.C: New test. * g++.dg/contracts/contracts-pre4.C: New test. * g++.dg/contracts/contracts-pre5.C: New test. * g++.dg/contracts/contracts-pre6.C: New test. * g++.dg/contracts/contracts-pre7.C: New test. * g++.dg/contracts/contracts-pre9.C: New test. * g++.dg/contracts/contracts-redecl1.C: New test. * g++.dg/contracts/contracts-redecl2.C: New test. * g++.dg/contracts/contracts-redecl3.C: New test. * g++.dg/contracts/contracts-redecl4.C: New test. * g++.dg/contracts/contracts-redecl5.C: New test. * g++.dg/contracts/contracts-redecl6.C: New test. * g++.dg/contracts/contracts-redecl7.C: New test. * g++.dg/contracts/contracts-redecl8.C: New test. * g++.dg/contracts/contracts-tmpl-attr1.C: New test. * g++.dg/contracts/contracts-tmpl-spec1.C: New test. * g++.dg/contracts/contracts-tmpl-spec2.C: New test. * g++.dg/contracts/contracts-tmpl-spec3.C: New test. * g++.dg/contracts/contracts1.C: New test. * g++.dg/contracts/contracts10.C: New test. * g++.dg/contracts/contracts11.C: New test. * g++.dg/contracts/contracts12.C: New test. * g++.dg/contracts/contracts13.C: New test. * g++.dg/contracts/contracts14.C: New test. * g++.dg/contracts/contracts15.C: New test. * g++.dg/contracts/contracts16.C: New test. * g++.dg/contracts/contracts17.C: New test. * g++.dg/contracts/contracts18.C: New test. * g++.dg/contracts/contracts19.C: New test. * g++.dg/contracts/contracts2.C: New test. * g++.dg/contracts/contracts20.C: New test. * g++.dg/contracts/contracts22.C: New test. * g++.dg/contracts/contracts24.C: New test. * g++.dg/contracts/contracts25.C: New test. * g++.dg/contracts/contracts3.C: New test. * g++.dg/contracts/contracts35.C: New test. * g++.dg/contracts/contracts4.C: New test. * g++.dg/contracts/contracts5.C: New test. * g++.dg/contracts/contracts6.C: New test. * g++.dg/contracts/contracts7.C: New test. * g++.dg/contracts/contracts8.C: New test. * g++.dg/contracts/contracts9.C: New test. * g++.dg/contracts/except_preload_handler/assert_fail.cpp: New test. * g++.dg/contracts/except_preload_handler/handle_contract_violation.cpp: New test. * g++.dg/contracts/noexcept_preload_handler/assert_fail.cpp: New test. * g++.dg/contracts/noexcept_preload_handler/handle_contract_violation.cpp: New test. * g++.dg/contracts/preload_handler/assert_fail.cpp: New test. * g++.dg/contracts/preload_handler/handle_contract_violation.cpp: New test. * g++.dg/contracts/preload_nocontinue_handler/assert_fail.cpp: New test. * g++.dg/contracts/preload_nocontinue_handler/handle_contract_violation.cpp: New test. * g++.dg/contracts/preload_nocontinue_handler/nocontinue.cpp: New test. * g++.dg/modules/contracts-1_a.C: New test. * g++.dg/modules/contracts-1_b.C: New test. * g++.dg/modules/contracts-2_a.C: New test. * g++.dg/modules/contracts-2_b.C: New test. * g++.dg/modules/contracts-2_c.C: New test. * g++.dg/modules/contracts-3_a.C: New test. * g++.dg/modules/contracts-3_b.C: New test. * g++.dg/modules/contracts-4_a.C: New test. * g++.dg/modules/contracts-4_b.C: New test. * g++.dg/modules/contracts-4_c.C: New test. * g++.dg/modules/contracts-4_d.C: New test. * g++.dg/modules/contracts-tpl-friend-1_a.C: New test. * g++.dg/modules/contracts-tpl-friend-1_b.C: New test. * g++.dg/contracts/backtrace_handler/Makefile: New test. * g++.dg/contracts/backtrace_handler/README: New test. * g++.dg/contracts/backtrace_handler/example_out.txt: New test. * g++.dg/contracts/backtrace_handler/example_pretty.txt: New test. * g++.dg/contracts/backtrace_handler/prettytrace.sh: New test. * g++.dg/contracts/except_preload_handler/Makefile: New test. * g++.dg/contracts/except_preload_handler/README: New test. * g++.dg/contracts/noexcept_preload_handler/Makefile: New test. * g++.dg/contracts/noexcept_preload_handler/README: New test. * g++.dg/contracts/preload_handler/Makefile: New test. * g++.dg/contracts/preload_handler/README: New test. * g++.dg/contracts/preload_nocontinue_handler/Makefile: New test. * g++.dg/contracts/preload_nocontinue_handler/README: New test. --- gcc/doc/invoke.texi | 79 + gcc/c-family/c.opt | 41 + gcc/cp/contracts.h | 305 +++ gcc/cp/cp-tree.h | 64 + gcc/cp/parser.h | 3 + gcc/c-family/c-cppbuiltin.cc | 6 + gcc/cp/constexpr.cc | 121 +- gcc/cp/contracts.cc | 2240 +++++++++++++++++ gcc/cp/cp-gimplify.cc | 17 + gcc/cp/cp-objcp-common.cc | 41 + gcc/cp/decl.cc | 115 +- gcc/cp/decl2.cc | 15 +- gcc/cp/error.cc | 3 +- gcc/cp/g++spec.cc | 12 + gcc/cp/mangle.cc | 7 + gcc/cp/module.cc | 30 +- gcc/cp/parser.cc | 522 +++- gcc/cp/pt.cc | 164 +- gcc/cp/search.cc | 28 + gcc/cp/semantics.cc | 28 +- gcc/cp/tree.cc | 65 + gcc/cp/typeck.cc | 13 +- .../backtrace_handler/assert_fail.cpp | 23 + .../handle_contract_violation.cpp | 26 + .../g++.dg/contracts/contracts-access1.C | 128 + .../g++.dg/contracts/contracts-assume1.C | 30 + .../g++.dg/contracts/contracts-assume2.C | 34 + .../g++.dg/contracts/contracts-assume3.C | 19 + .../g++.dg/contracts/contracts-assume4.C | 19 + .../g++.dg/contracts/contracts-assume5.C | 34 + .../g++.dg/contracts/contracts-assume6.C | 61 + .../g++.dg/contracts/contracts-comdat1.C | 19 + .../g++.dg/contracts/contracts-config1.C | 36 + .../g++.dg/contracts/contracts-constexpr1.C | 74 + .../g++.dg/contracts/contracts-constexpr2.C | 58 + .../g++.dg/contracts/contracts-constexpr3.C | 10 + .../g++.dg/contracts/contracts-conversion1.C | 19 + .../g++.dg/contracts/contracts-ctor-dtor1.C | 177 ++ .../g++.dg/contracts/contracts-ctor-dtor2.C | 35 + .../g++.dg/contracts/contracts-cv1.C | 37 + .../g++.dg/contracts/contracts-deduced1.C | 108 + .../g++.dg/contracts/contracts-deduced2.C | 84 + .../g++.dg/contracts/contracts-friend1.C | 40 + .../g++.dg/contracts/contracts-ft1.C | 14 + .../g++.dg/contracts/contracts-ignore1.C | 30 + .../g++.dg/contracts/contracts-ignore2.C | 26 + .../g++.dg/contracts/contracts-large-return.C | 15 + .../g++.dg/contracts/contracts-multiline1.C | 19 + .../contracts-multiple-inheritance1.C | 15 + .../contracts-multiple-inheritance2.C | 33 + .../contracts/contracts-nested-class1.C | 24 + .../contracts/contracts-nested-class2.C | 40 + .../g++.dg/contracts/contracts-nocopy1.C | 24 + .../g++.dg/contracts/contracts-override.C | 43 + .../g++.dg/contracts/contracts-post1.C | 74 + .../g++.dg/contracts/contracts-post2.C | 13 + .../g++.dg/contracts/contracts-post3.C | 15 + .../g++.dg/contracts/contracts-post4.C | 36 + .../g++.dg/contracts/contracts-post5.C | 19 + .../g++.dg/contracts/contracts-post6.C | 30 + .../g++.dg/contracts/contracts-pre1.C | 36 + .../g++.dg/contracts/contracts-pre10.C | 190 ++ .../g++.dg/contracts/contracts-pre2.C | 212 ++ .../g++.dg/contracts/contracts-pre2a1.C | 33 + .../g++.dg/contracts/contracts-pre2a2.C | 22 + .../g++.dg/contracts/contracts-pre3.C | 525 ++++ .../g++.dg/contracts/contracts-pre4.C | 92 + .../g++.dg/contracts/contracts-pre5.C | 81 + .../g++.dg/contracts/contracts-pre6.C | 74 + .../g++.dg/contracts/contracts-pre7.C | 134 + .../g++.dg/contracts/contracts-pre9.C | 146 ++ .../g++.dg/contracts/contracts-redecl1.C | 149 ++ .../g++.dg/contracts/contracts-redecl2.C | 149 ++ .../g++.dg/contracts/contracts-redecl3.C | 195 ++ .../g++.dg/contracts/contracts-redecl4.C | 56 + .../g++.dg/contracts/contracts-redecl5.C | 101 + .../g++.dg/contracts/contracts-redecl6.C | 195 ++ .../g++.dg/contracts/contracts-redecl7.C | 95 + .../g++.dg/contracts/contracts-redecl8.C | 64 + .../g++.dg/contracts/contracts-tmpl-attr1.C | 19 + .../g++.dg/contracts/contracts-tmpl-spec1.C | 121 + .../g++.dg/contracts/contracts-tmpl-spec2.C | 395 +++ .../g++.dg/contracts/contracts-tmpl-spec3.C | 45 + gcc/testsuite/g++.dg/contracts/contracts1.C | 49 + gcc/testsuite/g++.dg/contracts/contracts10.C | 73 + gcc/testsuite/g++.dg/contracts/contracts11.C | 103 + gcc/testsuite/g++.dg/contracts/contracts12.C | 15 + gcc/testsuite/g++.dg/contracts/contracts13.C | 51 + gcc/testsuite/g++.dg/contracts/contracts14.C | 58 + gcc/testsuite/g++.dg/contracts/contracts15.C | 56 + gcc/testsuite/g++.dg/contracts/contracts16.C | 34 + gcc/testsuite/g++.dg/contracts/contracts17.C | 35 + gcc/testsuite/g++.dg/contracts/contracts18.C | 15 + gcc/testsuite/g++.dg/contracts/contracts19.C | 19 + gcc/testsuite/g++.dg/contracts/contracts2.C | 13 + gcc/testsuite/g++.dg/contracts/contracts20.C | 11 + gcc/testsuite/g++.dg/contracts/contracts22.C | 32 + gcc/testsuite/g++.dg/contracts/contracts24.C | 15 + gcc/testsuite/g++.dg/contracts/contracts25.C | 57 + gcc/testsuite/g++.dg/contracts/contracts3.C | 13 + gcc/testsuite/g++.dg/contracts/contracts35.C | 47 + gcc/testsuite/g++.dg/contracts/contracts4.C | 11 + gcc/testsuite/g++.dg/contracts/contracts5.C | 13 + gcc/testsuite/g++.dg/contracts/contracts6.C | 11 + gcc/testsuite/g++.dg/contracts/contracts7.C | 14 + gcc/testsuite/g++.dg/contracts/contracts8.C | 43 + gcc/testsuite/g++.dg/contracts/contracts9.C | 45 + .../except_preload_handler/assert_fail.cpp | 20 + .../handle_contract_violation.cpp | 14 + .../noexcept_preload_handler/assert_fail.cpp | 20 + .../handle_contract_violation.cpp | 14 + .../contracts/preload_handler/assert_fail.cpp | 7 + .../handle_contract_violation.cpp | 15 + .../assert_fail.cpp | 10 + .../handle_contract_violation.cpp | 13 + .../preload_nocontinue_handler/nocontinue.cpp | 19 + gcc/testsuite/g++.dg/modules/contracts-1_a.C | 46 + gcc/testsuite/g++.dg/modules/contracts-1_b.C | 33 + gcc/testsuite/g++.dg/modules/contracts-2_a.C | 49 + gcc/testsuite/g++.dg/modules/contracts-2_b.C | 35 + gcc/testsuite/g++.dg/modules/contracts-2_c.C | 22 + gcc/testsuite/g++.dg/modules/contracts-3_a.C | 41 + gcc/testsuite/g++.dg/modules/contracts-3_b.C | 35 + gcc/testsuite/g++.dg/modules/contracts-4_a.C | 28 + gcc/testsuite/g++.dg/modules/contracts-4_b.C | 8 + gcc/testsuite/g++.dg/modules/contracts-4_c.C | 9 + gcc/testsuite/g++.dg/modules/contracts-4_d.C | 22 + .../g++.dg/modules/contracts-tpl-friend-1_a.C | 17 + .../g++.dg/modules/contracts-tpl-friend-1_b.C | 19 + gcc/cp/Make-lang.in | 2 +- gcc/cp/config-lang.in | 1 + gcc/cp/cp-tree.def | 11 + .../contracts/backtrace_handler/Makefile | 13 + .../g++.dg/contracts/backtrace_handler/README | 12 + .../backtrace_handler/example_out.txt | 12 + .../backtrace_handler/example_pretty.txt | 8 + .../backtrace_handler/prettytrace.sh | 30 + .../contracts/except_preload_handler/Makefile | 13 + .../contracts/except_preload_handler/README | 13 + .../noexcept_preload_handler/Makefile | 13 + .../contracts/noexcept_preload_handler/README | 15 + .../g++.dg/contracts/preload_handler/Makefile | 13 + .../g++.dg/contracts/preload_handler/README | 2 + .../preload_nocontinue_handler/Makefile | 23 + .../preload_nocontinue_handler/README | 23 + gcc/testsuite/g++.dg/modules/modules.exp | 9 +- gcc/testsuite/lib/g++.exp | 4 + 147 files changed, 10069 insertions(+), 66 deletions(-) create mode 100644 gcc/cp/contracts.h create mode 100644 gcc/cp/contracts.cc create mode 100644 gcc/testsuite/g++.dg/contracts/backtrace_handler/assert_fail.cpp create mode 100644 gcc/testsuite/g++.dg/contracts/backtrace_handler/handle_contract_violation.cpp create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-access1.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-assume1.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-assume2.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-assume3.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-assume4.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-assume5.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-assume6.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-comdat1.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-config1.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-constexpr1.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-constexpr2.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-constexpr3.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-conversion1.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-ctor-dtor1.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-ctor-dtor2.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-cv1.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-deduced1.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-deduced2.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-friend1.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-ft1.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-ignore1.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-ignore2.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-large-return.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-multiline1.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-multiple-inheritance1.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-multiple-inheritance2.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-nested-class1.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-nested-class2.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-nocopy1.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-override.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-post1.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-post2.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-post3.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-post4.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-post5.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-post6.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-pre1.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-pre10.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-pre2.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-pre2a1.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-pre2a2.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-pre3.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-pre4.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-pre5.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-pre6.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-pre7.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-pre9.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-redecl1.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-redecl2.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-redecl3.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-redecl4.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-redecl5.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-redecl6.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-redecl7.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-redecl8.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-tmpl-attr1.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-tmpl-spec1.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-tmpl-spec2.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts-tmpl-spec3.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts1.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts10.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts11.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts12.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts13.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts14.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts15.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts16.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts17.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts18.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts19.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts2.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts20.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts22.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts24.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts25.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts3.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts35.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts4.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts5.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts6.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts7.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts8.C create mode 100644 gcc/testsuite/g++.dg/contracts/contracts9.C create mode 100644 gcc/testsuite/g++.dg/contracts/except_preload_handler/assert_fail.cpp create mode 100644 gcc/testsuite/g++.dg/contracts/except_preload_handler/handle_contract_violation.cpp create mode 100644 gcc/testsuite/g++.dg/contracts/noexcept_preload_handler/assert_fail.cpp create mode 100644 gcc/testsuite/g++.dg/contracts/noexcept_preload_handler/handle_contract_violation.cpp create mode 100644 gcc/testsuite/g++.dg/contracts/preload_handler/assert_fail.cpp create mode 100644 gcc/testsuite/g++.dg/contracts/preload_handler/handle_contract_violation.cpp create mode 100644 gcc/testsuite/g++.dg/contracts/preload_nocontinue_handler/assert_fail.cpp create mode 100644 gcc/testsuite/g++.dg/contracts/preload_nocontinue_handler/handle_contract_violation.cpp create mode 100644 gcc/testsuite/g++.dg/contracts/preload_nocontinue_handler/nocontinue.cpp create mode 100644 gcc/testsuite/g++.dg/modules/contracts-1_a.C create mode 100644 gcc/testsuite/g++.dg/modules/contracts-1_b.C create mode 100644 gcc/testsuite/g++.dg/modules/contracts-2_a.C create mode 100644 gcc/testsuite/g++.dg/modules/contracts-2_b.C create mode 100644 gcc/testsuite/g++.dg/modules/contracts-2_c.C create mode 100644 gcc/testsuite/g++.dg/modules/contracts-3_a.C create mode 100644 gcc/testsuite/g++.dg/modules/contracts-3_b.C create mode 100644 gcc/testsuite/g++.dg/modules/contracts-4_a.C create mode 100644 gcc/testsuite/g++.dg/modules/contracts-4_b.C create mode 100644 gcc/testsuite/g++.dg/modules/contracts-4_c.C create mode 100644 gcc/testsuite/g++.dg/modules/contracts-4_d.C create mode 100644 gcc/testsuite/g++.dg/modules/contracts-tpl-friend-1_a.C create mode 100644 gcc/testsuite/g++.dg/modules/contracts-tpl-friend-1_b.C create mode 100644 gcc/testsuite/g++.dg/contracts/backtrace_handler/Makefile create mode 100644 gcc/testsuite/g++.dg/contracts/backtrace_handler/README create mode 100644 gcc/testsuite/g++.dg/contracts/backtrace_handler/example_out.txt create mode 100644 gcc/testsuite/g++.dg/contracts/backtrace_handler/example_pretty.txt create mode 100755 gcc/testsuite/g++.dg/contracts/backtrace_handler/prettytrace.sh create mode 100644 gcc/testsuite/g++.dg/contracts/except_preload_handler/Makefile create mode 100644 gcc/testsuite/g++.dg/contracts/except_preload_handler/README create mode 100644 gcc/testsuite/g++.dg/contracts/noexcept_preload_handler/Makefile create mode 100644 gcc/testsuite/g++.dg/contracts/noexcept_preload_handler/README create mode 100644 gcc/testsuite/g++.dg/contracts/preload_handler/Makefile create mode 100644 gcc/testsuite/g++.dg/contracts/preload_handler/README create mode 100644 gcc/testsuite/g++.dg/contracts/preload_nocontinue_handler/Makefile create mode 100644 gcc/testsuite/g++.dg/contracts/preload_nocontinue_handler/README diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index e9207a3a255..27ebb406083 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -3132,6 +3132,85 @@ of a loop too many expressions need to be evaluated, the resulting constexpr evaluation might take too long. The default is 33554432 (1<<25). +@item -fcontracts +@opindex fcontracts +Enable experimental support for the C++ Contracts feature, as briefly +added to and then removed from the C++20 working paper (N4820). The +implementation also includes proposed enhancements from papers P1290, +P1332, and P1429. This functionality is intended mostly for those +interested in experimentation towards refining the feature to get it +into shape for a future C++ standard. + +On violation of a checked contract, the violation handler is called. +Users can replace the violation handler by defining +@smallexample +void handle_contract_violation (const std::experimental::contract_violation&); +@end smallexample + +There are different sets of additional flags that can be used together +to specify which contracts will be checked and how, for N4820 +contracts, P1332 contracts, or P1429 contracts; these sets cannot be +used together. + +@table @gcctabopt +@item -fcontract-mode=[on|off] +@opindex fcontract-mode +Control whether any contracts have any semantics at all. Defaults to on. + +@item -fcontract-assumption-mode=[on|off] +@opindex fcontract-assumption-mode +[N4820] Control whether contracts with level @samp{axiom} +should have the assume semantic. Defaults to on. + +@item -fcontract-build-level=[off|default|audit] +@opindex fcontract-build-level +[N4820] Specify which level of contracts to generate checks +for. Defaults to @samp{default}. + +@item -fcontract-continuation-mode=[on|off] +@opindex fcontract-continuation-mode +[N4820] Control whether to allow the program to continue executing +after a contract violation. That is, do checked contracts have the +@samp{maybe} semantic described below rather than the @samp{never} +semantic. Defaults to off. + +@item -fcontract-role=:,, +@opindex fcontract-role +[P1332] Specify the concrete semantics for each contract level +of a particular contract role. + +@item -fcontract-semantic=[default|audit|axiom]: +[P1429] Specify the concrete semantic for a particular +contract level. + +@item -fcontract-strict-declarations=[on|off] +@opindex fcontract-strict-declarations +Control whether to reject adding contracts to a function after its +first declaration. Defaults to off. +@end table + +The possible concrete semantics for that can be specified with +@samp{-fcontract-role} or @samp{-fcontract-semantic} are: + +@table @code +@item ignore +This contract has no effect. + +@item assume +This contract is treated like C++23 @code{[[assume]]}. + +@item check_never_continue +@itemx never +@itemx abort +This contract is checked. If it fails, the violation handler is +called. If the handler returns, @code{std::terminate} is called. + +@item check_maybe_continue +@itemx maybe +This contract is checked. If it fails, the violation handler is +called. If the handler returns, execution continues normally. +@end table + @item -fcoroutines @opindex fcoroutines Enable support for the C++ coroutines extension (experimental). diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt index 63a300ecd7c..f08d38d9127 100644 --- a/gcc/c-family/c.opt +++ b/gcc/c-family/c.opt @@ -1674,6 +1674,47 @@ fconstexpr-ops-limit= C++ ObjC++ Joined RejectNegative Host_Wide_Int Var(constexpr_ops_limit) Init(33554432) -fconstexpr-ops-limit= Specify maximum number of constexpr operations during a single constexpr evaluation. +fcontracts +C++ ObjC++ Var(flag_contracts) Init(0) +Enable certain features present drafts of C++ Contracts. + +Enum +Name(on_off) Type(int) UnknownError(argument %qs must be either % or %) + +EnumValue +Enum(on_off) String(off) Value(0) + +EnumValue +Enum(on_off) String(on) Value(1) + +fcontract-assumption-mode= +C++ Joined +-fcontract-assumption-mode=[on|off] Enable or disable treating axiom level contracts as assumptions (default on). + +fcontract-build-level= +C++ Joined RejectNegative +-fcontract-build-level=[off|default|audit] Specify max contract level to generate runtime checks for + +fcontract-strict-declarations= +C++ Var(flag_contract_strict_declarations) Enum(on_off) Joined Init(0) RejectNegative +-fcontract-strict-declarations=[on|off] Enable or disable warnings on generalized redeclaration of functions with contracts (default off). + +fcontract-mode= +C++ Var(flag_contract_mode) Enum(on_off) Joined Init(1) RejectNegative +-fcontract-mode=[on|off] Enable or disable all contract facilities (default on). + +fcontract-continuation-mode= +C++ Joined +-fcontract-continuation-mode=[on|off] Enable or disable contract continuation mode (default off). + +fcontract-role= +C++ Joined +-fcontract-role=: Specify the semantics for all levels in a role (default, review), or a custom contract role with given semantics (ex: opt:assume,assume,assume) + +fcontract-semantic= +C++ Joined +-fcontract-semantic=: Specify the concrete semantics for level + fcoroutines C++ LTO Var(flag_coroutines) Enable C++ coroutines (experimental). diff --git a/gcc/cp/contracts.h b/gcc/cp/contracts.h new file mode 100644 index 00000000000..4050a38708b --- /dev/null +++ b/gcc/cp/contracts.h @@ -0,0 +1,305 @@ +/* Definitions for C++ contract levels. Implements functionality described in + the N4820 working draft version of contracts, P1290, P1332, and P1429. + Copyright (C) 2020-2022 Free Software Foundation, Inc. + Contributed by Jeff Chapman II (jchapman@lock3software.com) + +This file is part of GCC. + +GCC 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, or (at your option) +any later version. + +GCC 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 GCC; see the file COPYING3. If not see +. */ + +#ifndef GCC_CP_CONTRACT_H +#define GCC_CP_CONTRACT_H + +/* Contract levels approximate the complexity of the expression. */ + +enum contract_level +{ + CONTRACT_INVALID, + CONTRACT_DEFAULT, + CONTRACT_AUDIT, + CONTRACT_AXIOM +}; + +/* The concrete semantics determine the behavior of a contract. */ + +enum contract_semantic +{ + CCS_INVALID, + CCS_IGNORE, + CCS_ASSUME, + CCS_NEVER, + CCS_MAYBE +}; + +/* True if the contract is unchecked. */ + +inline bool +unchecked_contract_p (contract_semantic cs) +{ + return cs == CCS_IGNORE || cs == CCS_ASSUME; +} + +/* True if the contract is checked. */ + +inline bool +checked_contract_p (contract_semantic cs) +{ + return cs >= CCS_NEVER; +} + +/* Must match std::contract_violation_continuation_mode in . */ +enum contract_continuation +{ + NEVER_CONTINUE, + MAYBE_CONTINUE +}; + +/* Assertion role info. */ +struct contract_role +{ + const char *name; + contract_semantic default_semantic; + contract_semantic audit_semantic; + contract_semantic axiom_semantic; +}; + +/* Information for configured contract semantics. */ + +struct contract_configuration +{ + contract_level level; + contract_role* role; +}; + +/* A contract mode contains information used to derive the checking + and assumption semantics of a contract. This is either a dynamic + configuration, meaning it derives from the build mode, or it is + explicitly specified. */ + +struct contract_mode +{ + contract_mode () : kind(cm_invalid) {} + contract_mode (contract_level level, contract_role *role = NULL) + : kind(cm_dynamic) + { + contract_configuration cc; + cc.level = level; + cc.role = role; + u.config = cc; + } + contract_mode (contract_semantic semantic) : kind(cm_explicit) + { + u.semantic = semantic; + } + + contract_level get_level () const + { + gcc_assert (kind == cm_dynamic); + return u.config.level; + } + + contract_role *get_role () const + { + gcc_assert (kind == cm_dynamic); + return u.config.role; + } + + contract_semantic get_semantic () const + { + gcc_assert (kind == cm_explicit); + return u.semantic; + } + + enum { cm_invalid, cm_dynamic, cm_explicit } kind; + + union + { + contract_configuration config; + contract_semantic semantic; + } u; +}; + +extern contract_role *get_contract_role (const char *); +extern contract_role *add_contract_role (const char *, + contract_semantic, + contract_semantic, + contract_semantic, + bool = true); +extern void validate_contract_role (contract_role *); +extern void setup_default_contract_role (bool = true); +extern contract_semantic lookup_concrete_semantic (const char *); + +/* Map a source level semantic or level name to its value, or invalid. */ +extern contract_semantic map_contract_semantic (const char *); +extern contract_level map_contract_level (const char *); + +/* Check if an attribute is a cxx contract attribute. */ +extern bool cxx_contract_attribute_p (const_tree); +extern bool cp_contract_assertion_p (const_tree); + +/* Returns the default role. */ + +inline contract_role * +get_default_contract_role () +{ + return get_contract_role ("default"); +} + +/* Handle various command line arguments related to semantic mapping. */ +extern void handle_OPT_fcontract_build_level_ (const char *); +extern void handle_OPT_fcontract_assumption_mode_ (const char *); +extern void handle_OPT_fcontract_continuation_mode_ (const char *); +extern void handle_OPT_fcontract_role_ (const char *); +extern void handle_OPT_fcontract_semantic_ (const char *); + +enum contract_matching_context +{ + cmc_declaration, + cmc_override +}; + +/* True if NODE is any kind of contract. */ +#define CONTRACT_P(NODE) \ + (TREE_CODE (NODE) == ASSERTION_STMT \ + || TREE_CODE (NODE) == PRECONDITION_STMT \ + || TREE_CODE (NODE) == POSTCONDITION_STMT) + +/* True if NODE is a contract condition. */ +#define CONTRACT_CONDITION_P(NODE) \ + (TREE_CODE (NODE) == PRECONDITION_STMT \ + || TREE_CODE (NODE) == POSTCONDITION_STMT) + +/* True if NODE is a precondition. */ +#define PRECONDITION_P(NODE) \ + (TREE_CODE (NODE) == PRECONDITION_STMT) + +/* True if NODE is a postcondition. */ +#define POSTCONDITION_P(NODE) \ + (TREE_CODE (NODE) == POSTCONDITION_STMT) + +#define CONTRACT_CHECK(NODE) \ + (TREE_CHECK3 (NODE, ASSERTION_STMT, PRECONDITION_STMT, POSTCONDITION_STMT)) + +/* True iff the FUNCTION_DECL NODE currently has any contracts. */ +#define DECL_HAS_CONTRACTS_P(NODE) \ + (DECL_CONTRACTS (NODE) != NULL_TREE) + +/* For a FUNCTION_DECL of a guarded function, this points to a list of the pre + and post contracts of the first decl of NODE in original order. */ +#define DECL_CONTRACTS(NODE) \ + (find_contract (DECL_ATTRIBUTES (NODE))) + +/* The next contract (if any) after this one in an attribute list. */ +#define CONTRACT_CHAIN(NODE) \ + (find_contract (TREE_CHAIN (NODE))) + +/* The wrapper of the original source location of a list of contracts. */ +#define CONTRACT_SOURCE_LOCATION_WRAPPER(NODE) \ + (TREE_PURPOSE (TREE_VALUE (NODE))) + +/* The original source location of a list of contracts. */ +#define CONTRACT_SOURCE_LOCATION(NODE) \ + (EXPR_LOCATION (CONTRACT_SOURCE_LOCATION_WRAPPER (NODE))) + +/* The actual code _STMT for a contract attribute. */ +#define CONTRACT_STATEMENT(NODE) \ + (TREE_VALUE (TREE_VALUE (NODE))) + +/* True if the contract semantic was specified literally. If true, the + contract mode is an identifier containing the semantic. Otherwise, + it is a TREE_LIST whose TREE_VALUE is the level and whose TREE_PURPOSE + is the role. */ +#define CONTRACT_LITERAL_MODE_P(NODE) \ + (CONTRACT_MODE (NODE) != NULL_TREE \ + && TREE_CODE (CONTRACT_MODE (NODE)) == IDENTIFIER_NODE) + +/* The identifier denoting the literal semantic of the contract. */ +#define CONTRACT_LITERAL_SEMANTIC(NODE) \ + (TREE_OPERAND (NODE, 0)) + +/* The written "mode" of the contract. Either an IDENTIFIER with the + literal semantic or a TREE_LIST containing the level and role. */ +#define CONTRACT_MODE(NODE) \ + (TREE_OPERAND (CONTRACT_CHECK (NODE), 0)) + +/* The identifier denoting the build level of the contract. */ +#define CONTRACT_LEVEL(NODE) \ + (TREE_VALUE (CONTRACT_MODE (NODE))) + +/* The identifier denoting the role of the contract */ +#define CONTRACT_ROLE(NODE) \ + (TREE_PURPOSE (CONTRACT_MODE (NODE))) + +/* The parsed condition of the contract. */ +#define CONTRACT_CONDITION(NODE) \ + (TREE_OPERAND (CONTRACT_CHECK (NODE), 1)) + +/* True iff the condition of the contract NODE is not yet parsed. */ +#define CONTRACT_CONDITION_DEFERRED_P(NODE) \ + (TREE_CODE (CONTRACT_CONDITION (NODE)) == DEFERRED_PARSE) + +/* The raw comment of the contract. */ +#define CONTRACT_COMMENT(NODE) \ + (TREE_OPERAND (CONTRACT_CHECK (NODE), 2)) + +/* The VAR_DECL of a postcondition result. For deferred contracts, this + is an IDENTIFIER. */ +#define POSTCONDITION_IDENTIFIER(NODE) \ + (TREE_OPERAND (POSTCONDITION_STMT_CHECK (NODE), 3)) + +/* For a FUNCTION_DECL of a guarded function, this holds the function decl + where pre contract checks are emitted. */ +#define DECL_PRE_FN(NODE) \ + (get_precondition_function ((NODE))) + +/* For a FUNCTION_DECL of a guarded function, this holds the function decl + where post contract checks are emitted. */ +#define DECL_POST_FN(NODE) \ + (get_postcondition_function ((NODE))) + +/* True iff the FUNCTION_DECL is the pre function for a guarded function. */ +#define DECL_IS_PRE_FN_P(NODE) \ + (DECL_ABSTRACT_ORIGIN (NODE) && DECL_PRE_FN (DECL_ABSTRACT_ORIGIN (NODE)) == NODE) + +/* True iff the FUNCTION_DECL is the post function for a guarded function. */ +#define DECL_IS_POST_FN_P(NODE) \ + (DECL_ABSTRACT_ORIGIN (NODE) && DECL_POST_FN (DECL_ABSTRACT_ORIGIN (NODE)) == NODE) + +extern void remove_contract_attributes (tree); +extern void copy_contract_attributes (tree, tree); +extern void remap_contracts (tree, tree, tree, bool); +extern void maybe_update_postconditions (tree); +extern void rebuild_postconditions (tree); +extern bool check_postcondition_result (tree, tree, location_t); +extern tree get_precondition_function (tree); +extern tree get_postcondition_function (tree); +extern void duplicate_contracts (tree, tree); +extern void match_deferred_contracts (tree); +extern void defer_guarded_contract_match (tree, tree, tree); +extern bool diagnose_misapplied_contracts (tree); +extern tree finish_contract_attribute (tree, tree); +extern tree invalidate_contract (tree); +extern void update_late_contract (tree, tree, tree); +extern tree splice_out_contracts (tree); +extern bool all_attributes_are_contracts_p (tree); +extern void inherit_base_contracts (tree, tree); +extern tree apply_postcondition_to_return (tree); +extern void start_function_contracts (tree); +extern void finish_function_contracts (tree); +extern void set_contract_functions (tree, tree, tree); +extern tree build_contract_check (tree); +extern void emit_assertion (tree); + +#endif /* ! GCC_CP_CONTRACT_H */ diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index d13bb3d4c0e..ae3cdfb18ee 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -25,6 +25,7 @@ along with GCC; see the file COPYING3. If not see #include "hard-reg-set.h" #include "function.h" #include "tristate.h" +#include "contracts.h" /* In order for the format checking to accept the C++ front end diagnostic framework extensions, you must include this file before @@ -232,6 +233,8 @@ enum cp_tree_index CPTI_DSO_HANDLE, CPTI_DCAST, + CPTI_PSEUDO_CONTRACT_VIOLATION, + CPTI_SOURCE_LOCATION_IMPL, CPTI_FALLBACK_DFLOAT32_TYPE, @@ -266,6 +269,7 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX]; #define current_aggr cp_global_trees[CPTI_AGGR_TAG] /* std::align_val_t */ #define align_type_node cp_global_trees[CPTI_ALIGN_TYPE] +#define pseudo_contract_violation_type cp_global_trees[CPTI_PSEUDO_CONTRACT_VIOLATION] /* We cache these tree nodes so as to call get_identifier less frequently. For identifiers for functions, including special member functions such @@ -455,6 +459,7 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX]; ALIGNOF_EXPR_STD_P (in ALIGNOF_EXPR) OVL_DEDUP_P (in OVERLOAD) ATOMIC_CONSTR_MAP_INSTANTIATED_P (in ATOMIC_CONSTR) + contract_semantic (in ASSERTION_, PRECONDITION_, POSTCONDITION_STMT) 1: IDENTIFIER_KIND_BIT_1 (in IDENTIFIER_NODE) TI_PENDING_TEMPLATE_FLAG. TEMPLATE_PARMS_FOR_INLINE. @@ -493,6 +498,7 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX]; LAMBDA_EXPR_CAPTURE_OPTIMIZED (in LAMBDA_EXPR) IMPLICIT_CONV_EXPR_BRACED_INIT (in IMPLICIT_CONV_EXPR) PACK_EXPANSION_AUTO_P (in *_PACK_EXPANSION) + contract_semantic (in ASSERTION_, PRECONDITION_, POSTCONDITION_STMT) 3: IMPLICIT_RVALUE_P (in NON_LVALUE_EXPR or STATIC_CAST_EXPR) ICS_BAD_FLAG (in _CONV) FN_TRY_BLOCK_P (in TRY_BLOCK) @@ -505,6 +511,7 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX]; PACK_EXPANSION_FORCE_EXTRA_ARGS_P (in *_PACK_EXPANSION) LAMBDA_EXPR_STATIC_P (in LAMBDA_EXPR) TARGET_EXPR_ELIDING_P (in TARGET_EXPR) + contract_semantic (in ASSERTION_, PRECONDITION_, POSTCONDITION_STMT) 4: IDENTIFIER_MARKED (IDENTIFIER_NODEs) TREE_HAS_CONSTRUCTOR (in INDIRECT_REF, SAVE_EXPR, CONSTRUCTOR, CALL_EXPR, or FIELD_DECL). @@ -1863,6 +1870,7 @@ struct GTY(()) saved_scope { int x_processing_template_decl; int x_processing_specialization; int x_processing_constraint; + int x_processing_contract_condition; int suppress_location_wrappers; BOOL_BITFIELD x_processing_explicit_instantiation : 1; BOOL_BITFIELD need_pop_function_context : 1; @@ -1937,6 +1945,12 @@ extern GTY(()) struct saved_scope *scope_chain; #define processing_specialization scope_chain->x_processing_specialization #define processing_explicit_instantiation scope_chain->x_processing_explicit_instantiation +/* Nonzero if we are parsing the conditional expression of a contract + condition. These expressions appear outside the paramter list (like a + trailing return type), but are potentially evaluated. */ + +#define processing_contract_condition scope_chain->x_processing_contract_condition + #define in_discarded_stmt scope_chain->discarded_stmt #define in_consteval_if_p scope_chain->consteval_if_p @@ -5651,6 +5665,11 @@ extern int comparing_specializations; FIXME we should always do this except during deduction/ordering. */ extern int comparing_dependent_aliases; +/* Nonzero if we want to consider different member expressions to compare + equal if they designate the same entity. This is set when comparing + contract conditions of overrides. */ +extern bool comparing_override_contracts; + /* In parser.cc. */ /* Nonzero if we are parsing an unevaluated operand: an operand to @@ -7454,8 +7473,10 @@ extern hashval_t iterative_hash_template_arg (tree arg, hashval_t val); extern tree coerce_template_parms (tree, tree, tree, tsubst_flags_t, bool = true); extern tree canonicalize_type_argument (tree, tsubst_flags_t); +extern void register_local_identity (tree); extern void register_local_specialization (tree, tree); extern tree retrieve_local_specialization (tree); +extern void register_parameter_specializations (tree, tree); extern tree extract_fnparm_pack (tree, tree *); extern tree template_parm_to_arg (tree); extern tree dguide_name (tree); @@ -8526,6 +8547,49 @@ extern tree coro_get_actor_function (tree); extern tree coro_get_destroy_function (tree); extern tree coro_get_ramp_function (tree); +/* contracts.cc */ +extern tree make_postcondition_variable (cp_expr); +extern tree make_postcondition_variable (cp_expr, tree); +extern tree grok_contract (tree, tree, tree, cp_expr, location_t); +extern tree finish_contract_condition (cp_expr); + +/* Return the first contract in ATTRS, or NULL_TREE if there are none. */ + +inline tree +find_contract (tree attrs) +{ + while (attrs && !cxx_contract_attribute_p (attrs)) + attrs = TREE_CHAIN (attrs); + return attrs; +} + +inline void +set_decl_contracts (tree decl, tree contract_attrs) +{ + remove_contract_attributes (decl); + DECL_ATTRIBUTES (decl) = chainon (DECL_ATTRIBUTES (decl), contract_attrs); +} + +/* Returns the computed semantic of the node. */ + +inline contract_semantic +get_contract_semantic (const_tree t) +{ + return (contract_semantic) (TREE_LANG_FLAG_3 (CONTRACT_CHECK (t)) + | (TREE_LANG_FLAG_2 (t) << 1) + | (TREE_LANG_FLAG_0 ((t)) << 2)); +} + +/* Sets the computed semantic of the node. */ + +inline void +set_contract_semantic (tree t, contract_semantic semantic) +{ + TREE_LANG_FLAG_3 (CONTRACT_CHECK (t)) = semantic & 0x01; + TREE_LANG_FLAG_2 (t) = (semantic & 0x02) >> 1; + TREE_LANG_FLAG_0 (t) = (semantic & 0x04) >> 2; +} + /* Inline bodies. */ inline tree diff --git a/gcc/cp/parser.h b/gcc/cp/parser.h index 3e95bfc131b..5737146dd42 100644 --- a/gcc/cp/parser.h +++ b/gcc/cp/parser.h @@ -178,6 +178,9 @@ struct GTY(()) cp_unparsed_functions_entry { /* Functions with noexcept-specifiers that require post-processing. */ vec *noexcepts; + + /* Functions with contract attributes that require post-processing. */ + vec *contracts; }; diff --git a/gcc/c-family/c-cppbuiltin.cc b/gcc/c-family/c-cppbuiltin.cc index cdb658f6ac9..f27dc2db794 100644 --- a/gcc/c-family/c-cppbuiltin.cc +++ b/gcc/c-family/c-cppbuiltin.cc @@ -1087,6 +1087,12 @@ c_cpp_builtins (cpp_reader *pfile) else cpp_define (pfile, "__cpp_concepts=201507L"); } + if (flag_contracts) + { + cpp_define (pfile, "__cpp_contracts=201906L"); + cpp_define (pfile, "__cpp_contracts_literal_semantics=201906L"); + cpp_define (pfile, "__cpp_contracts_roles=201906L"); + } if (flag_modules) /* The std-defined value is 201907L, but I don't think we can claim victory yet. 201810 is the p1103 date. */ diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc index 15b4f2c4a08..8a858be99f7 100644 --- a/gcc/cp/constexpr.cc +++ b/gcc/cp/constexpr.cc @@ -39,6 +39,7 @@ along with GCC; see the file COPYING3. If not see #include "stringpool.h" #include "attribs.h" #include "fold-const.h" +#include "intl.h" static bool verify_constant (tree, bool, bool *, bool *); #define VERIFY_CONSTANT(X) \ @@ -1936,6 +1937,61 @@ diagnose_failing_condition (tree bad, location_t cloc, bool show_expr_p, inform (cloc, "%qE evaluates to false", bad); } +/* Process an assert/assume of ORIG_ARG. If it's not supposed to be evaluated, + do it without changing the current evaluation state. If it evaluates to + false, complain and return false; otherwise, return true. */ + +static bool +cxx_eval_assert (const constexpr_ctx *ctx, tree arg, const char *msg, + location_t loc, bool evaluated, + bool *non_constant_p, bool *overflow_p) +{ + if (*non_constant_p) + return true; + + tree eval; + if (!evaluated) + { + if (!potential_rvalue_constant_expression (arg)) + return true; + + constexpr_ctx new_ctx = *ctx; + new_ctx.quiet = true; + bool new_non_constant_p = false, new_overflow_p = false; + /* Avoid modification of existing values. */ + modifiable_tracker ms (new_ctx.global); + eval = cxx_eval_constant_expression (&new_ctx, arg, vc_prvalue, + &new_non_constant_p, + &new_overflow_p); + } + else + eval = cxx_eval_constant_expression (ctx, arg, vc_prvalue, + non_constant_p, + overflow_p); + if (!*non_constant_p && integer_zerop (eval)) + { + if (!ctx->quiet) + { + /* See if we can find which clause was failing + (for logical AND). */ + tree bad = find_failing_clause (ctx, arg); + /* If not, or its location is unusable, fall back to the + previous location. */ + location_t cloc = cp_expr_loc_or_loc (bad, loc); + + /* Report the error. */ + auto_diagnostic_group d; + error_at (cloc, msg); + diagnose_failing_condition (bad, cloc, true, ctx); + return bad; + } + *non_constant_p = true; + return false; + } + + return true; +} + /* Evaluate a call T to a GCC internal function when possible and return the evaluated result or, under the control of CTX, give an error, set NON_CONSTANT_P, and return the unevaluated call T otherwise. */ @@ -1956,41 +2012,11 @@ cxx_eval_internal_function (const constexpr_ctx *ctx, tree t, return void_node; case IFN_ASSUME: - if (potential_rvalue_constant_expression (CALL_EXPR_ARG (t, 0))) - { - constexpr_ctx new_ctx = *ctx; - new_ctx.quiet = true; - tree arg = CALL_EXPR_ARG (t, 0); - bool new_non_constant_p = false, new_overflow_p = false; - /* Avoid modification of existing values. */ - modifiable_tracker ms (new_ctx.global); - arg = cxx_eval_constant_expression (&new_ctx, arg, vc_prvalue, - &new_non_constant_p, - &new_overflow_p); - if (!new_non_constant_p && !new_overflow_p && integer_zerop (arg)) - { - if (!*non_constant_p && !ctx->quiet) - { - /* See if we can find which clause was failing - (for logical AND). */ - tree bad = find_failing_clause (&new_ctx, - CALL_EXPR_ARG (t, 0)); - /* If not, or its location is unusable, fall back to the - previous location. */ - location_t cloc = cp_expr_loc_or_loc (bad, EXPR_LOCATION (t)); - - auto_diagnostic_group d; - - /* Report the error. */ - error_at (cloc, - "failed % attribute assumption"); - diagnose_failing_condition (bad, cloc, false, &new_ctx); - } - - *non_constant_p = true; - return t; - } - } + if (!cxx_eval_assert (ctx, CALL_EXPR_ARG (t, 0), + G_("failed % attribute assumption"), + EXPR_LOCATION (t), /*eval*/false, + non_constant_p, overflow_p)) + return t; return void_node; case IFN_ADD_OVERFLOW: @@ -7845,6 +7871,24 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t, r = void_node; break; + case ASSERTION_STMT: + case PRECONDITION_STMT: + case POSTCONDITION_STMT: + { + contract_semantic semantic = get_contract_semantic (t); + if (semantic == CCS_IGNORE) + break; + + if (!cxx_eval_assert (ctx, CONTRACT_CONDITION (t), + G_("contract predicate is false in " + "constant expression"), + EXPR_LOCATION (t), checked_contract_p (semantic), + non_constant_p, overflow_p)) + *non_constant_p = true; + r = void_node; + } + break; + case TEMPLATE_ID_EXPR: { /* We can evaluate template-id that refers to a concept only if @@ -9819,6 +9863,13 @@ potential_constant_expression_1 (tree t, bool want_rval, bool strict, bool now, return false; } + case ASSERTION_STMT: + case PRECONDITION_STMT: + case POSTCONDITION_STMT: + if (!checked_contract_p (get_contract_semantic (t))) + return true; + return RECUR (CONTRACT_CONDITION (t), rval); + case LABEL_EXPR: t = LABEL_EXPR_LABEL (t); if (DECL_ARTIFICIAL (t) || cxx_dialect >= cxx23) diff --git a/gcc/cp/contracts.cc b/gcc/cp/contracts.cc new file mode 100644 index 00000000000..79374344207 --- /dev/null +++ b/gcc/cp/contracts.cc @@ -0,0 +1,2240 @@ +/* Definitions for C++ contract levels + Copyright (C) 2020-2022 Free Software Foundation, Inc. + Contributed by Jeff Chapman II (jchapman@lock3software.com) + +This file is part of GCC. + +GCC 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, or (at your option) +any later version. + +GCC 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 GCC; see the file COPYING3. If not see +. */ + +/* Design Notes + + A function is called a "guarded" function if it has pre or post contract + attributes. A contract is considered an "active" contract if runtime code is + needed for the contract under the current contract configuration. + + pre and post contract attributes are parsed and stored in DECL_ATTRIBUTES. + assert contracts are parsed and wrapped in statements. When genericizing, all + active and assumed contracts are transformed into an if block. An observed + contract: + + [[ pre: v > 0 ]] + + is transformed into: + + if (!(v > 0)) { + handle_contract_violation(__pseudo_contract_violation{ + 5, // line_number, + "main.cpp", // file_name, + "fun", // function_name, + "v > 0", // comment, + "default", // assertion_level, + "default", // assertion_role, + MAYBE_CONTINUE, // continuation_mode + }); + terminate (); // if NEVER_CONTINUE + } + + We use an internal type with the same layout as contract_violation rather + than try to define the latter internally and somehow deal with its actual + definition in a TU that includes . + + ??? is it worth factoring out the calls to handle_contract_violation and + terminate into a local function? + + Assumed contracts use the same implementation as C++23 [[assume]]. + + Parsing of pre and post contract conditions need to be deferred when the + contracts are attached to a member function. The postcondition identifier + cannot be used before the deduced return type of an auto function is used, + except when used in a defining declaration in which case they conditions are + fully parsed once the body is finished (see cpp2a/contracts-deduced{1,2}.C). + + A list of pre and post contracts can either be repeated in their entirety or + completely absent in subsequent declarations. If contract lists appear on two + matching declarations, their contracts have to be equivalent. In general this + means that anything before the colon have to be token equivalent and the + condition must be cp_tree_equal (primarily to allow for parameter renaming). + + Contracts on overrides must match those present on (all of) the overridee(s). + + Template specializations may have their own contracts. If no contracts are + specified on the initial specialization they're assumed to be the same as + the primary template. Specialization redeclarations must then match either + the primary template (if they were unspecified originally), or those + specified on the specialization. + + + For non-cdtors two functions are generated for ease of implementation and to + avoid some cases where code bloat may occurr. These are the DECL_PRE_FN and + DECL_POST_FN. Each handles checking either the set of pre or post contracts + of a guarded function. + + int fun(int v) + [[ pre: v > 0 ]] + [[ post r: r < 0 ]] + { + return -v; + } + + The original decl is left alone and instead calls are generated to pre/post + functions within the body: + + void fun.pre(int v) + { + [[ assert: v > 0 ]]; + } + int fun.post(int v, int __r) + { + [[ assert: __r < 0 ]]; + return __r; + } + int fun(int v) + { + fun.pre(v); + return fun.post(v, -v); + } + + If fun returns in memory, the return value is not passed through the post + function; instead, the return object is initialized directly and then passed + to the post function by invisible reference. + + This sides steps a number of issues with having to rewrite the bodies or + rewrite the parsed conditions as the parameters to the original function + changes (as happens during redeclaration). The ultimate goal is to get + something that optimizes well along the lines of + + int fun(int v) + { + [[ assert: v > 0 ]]; + auto &&__r = -v; + goto out; + out: + [[ assert: __r < 0 ]]; + return __r; + } + + With the idea being that multiple return statements could collapse the + function epilogue after inlining the pre/post functions. clang is able + to collapse common function epilogues, while gcc needs -O3 -Os combined. + + Directly laying the pre contracts down in the function body doesn't have + many issues. The post contracts may need to be repeated multiple times, once + for each return, or a goto epilogue would need to be generated. + For this initial implementation, generating function calls and letting + later optimizations decide whether to inline and duplicate the actual + checks or whether to collapse the shared epilogue was chosen. + + For cdtors a post contract is implemented using a CLEANUP_STMT. + + FIXME the compiler already shores cleanup code on multiple exit paths, so + this outlining seems unnecessary if we represent the postcondition as a + cleanup for all functions. + + More helpful for optimization might be to make the contracts a wrapper + function (for non-variadic functions), that could be inlined into a + caller while preserving the call to the actual function? Either that or + mirror a never-continue post contract with an assume in the caller. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "cp-tree.h" +#include "stringpool.h" +#include "diagnostic.h" +#include "options.h" +#include "contracts.h" +#include "tree.h" +#include "tree-inline.h" +#include "attribs.h" +#include "tree-iterator.h" +#include "print-tree.h" +#include "stor-layout.h" + +const int max_custom_roles = 32; +static contract_role contract_build_roles[max_custom_roles] = { +}; + +bool valid_configs[CCS_MAYBE + 1][CCS_MAYBE + 1] = { + { 0, 0, 0, 0, 0, }, + { 0, 1, 0, 0, 0, }, + { 0, 1, 1, 1, 1, }, + { 0, 1, 1, 1, 1, }, + { 0, 1, 0, 0, 1, }, +}; + +void +validate_contract_role (contract_role *role) +{ + gcc_assert (role); + if (!unchecked_contract_p (role->axiom_semantic)) + error ("axiom contract semantic must be % or %"); + + if (!valid_configs[role->default_semantic][role->audit_semantic] ) + warning (0, "the % semantic should be at least as strong as " + "the % semantic"); +} + +contract_semantic +lookup_concrete_semantic (const char *name) +{ + if (strcmp (name, "ignore") == 0) + return CCS_IGNORE; + if (strcmp (name, "assume") == 0) + return CCS_ASSUME; + if (strcmp (name, "check_never_continue") == 0 + || strcmp (name, "never") == 0 + || strcmp (name, "abort") == 0) + return CCS_NEVER; + if (strcmp (name, "check_maybe_continue") == 0 + || strcmp (name, "maybe") == 0) + return CCS_MAYBE; + error ("'%s' is not a valid explicit concrete semantic", name); + return CCS_INVALID; +} + +/* Compare role and name up to either the NUL terminator or the first + occurrence of colon. */ + +static bool +role_name_equal (const char *role, const char *name) +{ + size_t role_len = strchrnul (role, ':') - role; + size_t name_len = strchrnul (name, ':') - name; + if (role_len != name_len) + return false; + return strncmp (role, name, role_len) == 0; +} + +static bool +role_name_equal (contract_role *role, const char *name) +{ + if (role->name == NULL) + return false; + return role_name_equal (role->name, name); +} + +contract_role * +get_contract_role (const char *name) +{ + for (int i = 0; i < max_custom_roles; ++i) + { + contract_role *potential = contract_build_roles + i; + if (role_name_equal (potential, name)) + return potential; + } + if (role_name_equal (name, "default") || role_name_equal (name, "review")) + { + setup_default_contract_role (false); + return get_contract_role (name); + } + return NULL; +} + +contract_role * +add_contract_role (const char *name, + contract_semantic des, + contract_semantic aus, + contract_semantic axs, + bool update) +{ + for (int i = 0; i < max_custom_roles; ++i) + { + contract_role *potential = contract_build_roles + i; + if (potential->name != NULL + && !role_name_equal (potential, name)) + continue; + if (potential->name != NULL && !update) + return potential; + potential->name = name; + potential->default_semantic = des; + potential->audit_semantic = aus; + potential->axiom_semantic = axs; + return potential; + } + return NULL; +} + +enum contract_build_level { OFF, DEFAULT, AUDIT }; +static bool flag_contract_continuation_mode = false; +static bool flag_contract_assumption_mode = true; +static int flag_contract_build_level = DEFAULT; + +static bool contracts_p1332_default = false, contracts_p1332_review = false, + contracts_std = false, contracts_p1429 = false; + +static contract_semantic +get_concrete_check () +{ + return flag_contract_continuation_mode ? CCS_MAYBE : CCS_NEVER; +} + +static contract_semantic +get_concrete_axiom_semantic () +{ + return flag_contract_assumption_mode ? CCS_ASSUME : CCS_IGNORE; +} + +void +setup_default_contract_role (bool update) +{ + contract_semantic check = get_concrete_check (); + contract_semantic axiom = get_concrete_axiom_semantic (); + switch (flag_contract_build_level) + { + case OFF: + add_contract_role ("default", CCS_IGNORE, CCS_IGNORE, axiom, update); + add_contract_role ("review", CCS_IGNORE, CCS_IGNORE, CCS_IGNORE, update); + break; + case DEFAULT: + add_contract_role ("default", check, CCS_IGNORE, axiom, update); + add_contract_role ("review", check, CCS_IGNORE, CCS_IGNORE, update); + break; + case AUDIT: + add_contract_role ("default", check, check, axiom, update); + add_contract_role ("review", check, check, CCS_IGNORE, update); + break; + } +} + +contract_semantic +map_contract_semantic (const char *ident) +{ + if (strcmp (ident, "ignore") == 0) + return CCS_IGNORE; + else if (strcmp (ident, "assume") == 0) + return CCS_ASSUME; + else if (strcmp (ident, "check_never_continue") == 0) + return CCS_NEVER; + else if (strcmp (ident, "check_maybe_continue") == 0) + return CCS_MAYBE; + return CCS_INVALID; +} + +contract_level +map_contract_level (const char *ident) +{ + if (strcmp (ident, "default") == 0) + return CONTRACT_DEFAULT; + else if (strcmp (ident, "audit") == 0) + return CONTRACT_AUDIT; + else if (strcmp (ident, "axiom") == 0) + return CONTRACT_AXIOM; + return CONTRACT_INVALID; +} + + +void +handle_OPT_fcontract_build_level_ (const char *arg) +{ + if (contracts_p1332_default || contracts_p1332_review || contracts_p1429) + { + error ("%<-fcontract-build-level=%> cannot be mixed with p1332/p1429"); + return; + } + else + contracts_std = true; + + if (strcmp (arg, "off") == 0) + flag_contract_build_level = OFF; + else if (strcmp (arg, "default") == 0) + flag_contract_build_level = DEFAULT; + else if (strcmp (arg, "audit") == 0) + flag_contract_build_level = AUDIT; + else + error ("%<-fcontract-build-level=%> must be off|default|audit"); + + setup_default_contract_role (); +} + +void +handle_OPT_fcontract_assumption_mode_ (const char *arg) +{ + if (contracts_p1332_default || contracts_p1332_review || contracts_p1429) + { + error ("%<-fcontract-assumption-mode=%> cannot be mixed with p1332/p1429"); + return; + } + else + contracts_std = true; + + if (strcmp (arg, "on") == 0) + flag_contract_assumption_mode = true; + else if (strcmp (arg, "off") == 0) + flag_contract_assumption_mode = false; + else + error ("%<-fcontract-assumption-mode=%> must be % or %"); + + setup_default_contract_role (); +} + +void +handle_OPT_fcontract_continuation_mode_ (const char *arg) +{ + if (contracts_p1332_default || contracts_p1332_review || contracts_p1429) + { + error ("%<-fcontract-continuation-mode=%> cannot be mixed with p1332/p1429"); + return; + } + else + contracts_std = true; + + if (strcmp (arg, "on") == 0) + flag_contract_continuation_mode = true; + else if (strcmp (arg, "off") == 0) + flag_contract_continuation_mode = false; + else + error ("%<-fcontract-continuation-mode=%> must be % or %"); + + setup_default_contract_role (); +} + +void +handle_OPT_fcontract_role_ (const char *arg) +{ + const char *name = arg; + const char *vals = strchr (name, ':'); + if (vals == NULL) + { + error ("%<-fcontract-role=%> must be in the form role:semantics"); + return; + } + + contract_semantic dess = CCS_INVALID, auss = CCS_INVALID, axss = CCS_INVALID; + char *des = NULL, *aus = NULL, *axs = NULL; + des = xstrdup (vals + 1); + + aus = strchr (des, ','); + if (aus == NULL) + { + error ("%<-fcontract-role=%> semantics must include default,audit,axiom values"); + goto validate; + } + *aus = '\0'; // null terminate des + aus = aus + 1; // move past null + + axs = strchr (aus, ','); + if (axs == NULL) + { + error ("%<-fcontract-role=%> semantics must include default,audit,axiom values"); + goto validate; + } + *axs = '\0'; // null terminate aus + axs = axs + 1; // move past null + + dess = lookup_concrete_semantic (des); + auss = lookup_concrete_semantic (aus); + axss = lookup_concrete_semantic (axs); +validate: + free (des); + if (dess == CCS_INVALID || auss == CCS_INVALID || axss == CCS_INVALID) + return; + + bool is_defalult_role = role_name_equal (name, "default"); + bool is_review_role = role_name_equal (name, "review"); + bool is_std_role = is_defalult_role || is_review_role; + if ((contracts_std && is_std_role) || (contracts_p1429 && is_defalult_role)) + { + error ("%<-fcontract-role=%> cannot be mixed with std/p1429 contract flags"); + return; + } + else if (is_std_role) + { + contracts_p1332_default |= is_defalult_role; + contracts_p1332_review |= is_review_role; + } + + contract_role *role = add_contract_role (name, dess, auss, axss); + + if (role == NULL) + { + // TODO: not enough space? + error ("%<-fcontract-level=%> too many custom roles"); + return; + } + else + validate_contract_role (role); +} + +void +handle_OPT_fcontract_semantic_ (const char *arg) +{ + if (!strchr (arg, ':')) + { + error ("%<-fcontract-semantic=%> must be in the form level:semantic"); + return; + } + + if (contracts_std || contracts_p1332_default) + { + error ("%<-fcontract-semantic=%> cannot be mixed with std/p1332 contract flags"); + return; + } + contracts_p1429 = true; + + contract_role *role = get_contract_role ("default"); + if (!role) + { + error ("%<-fcontract-semantic=%> cannot find default role"); + return; + } + + const char *semantic = strchr (arg, ':') + 1; + contract_semantic sem = lookup_concrete_semantic (semantic); + if (sem == CCS_INVALID) + return; + + if (strncmp ("default:", arg, 8) == 0) + role->default_semantic = sem; + else if (strncmp ("audit:", arg, 6) == 0) + role->audit_semantic = sem; + else if (strncmp ("axiom:", arg, 6) == 0) + role->axiom_semantic = sem; + else + error ("%<-fcontract-semantic=%> level must be default, audit, or axiom"); + validate_contract_role (role); +} + +/* Convert a contract CONFIG into a contract_mode. */ + +static contract_mode +contract_config_to_mode (tree config) +{ + if (config == NULL_TREE) + return contract_mode (CONTRACT_DEFAULT, get_default_contract_role ()); + + /* TREE_LIST has TREE_VALUE is a level and TREE_PURPOSE is role. */ + if (TREE_CODE (config) == TREE_LIST) + { + contract_role *role = NULL; + if (TREE_PURPOSE (config)) + role = get_contract_role (IDENTIFIER_POINTER (TREE_PURPOSE (config))); + if (!role) + role = get_default_contract_role (); + + contract_level level = + map_contract_level (IDENTIFIER_POINTER (TREE_VALUE (config))); + return contract_mode (level, role); + } + + /* Literal semantic. */ + gcc_assert (TREE_CODE (config) == IDENTIFIER_NODE); + contract_semantic semantic = + map_contract_semantic (IDENTIFIER_POINTER (config)); + return contract_mode (semantic); +} + +/* Convert a contract's config into a concrete semantic using the current + contract semantic mapping. */ + +static contract_semantic +compute_concrete_semantic (tree contract) +{ + contract_mode mode = contract_config_to_mode (CONTRACT_MODE (contract)); + /* Compute the concrete semantic for the contract. */ + if (!flag_contract_mode) + /* If contracts are off, treat all contracts as ignore. */ + return CCS_IGNORE; + else if (mode.kind == contract_mode::cm_invalid) + return CCS_INVALID; + else if (mode.kind == contract_mode::cm_explicit) + return mode.get_semantic (); + else + { + gcc_assert (mode.get_role ()); + gcc_assert (mode.get_level () != CONTRACT_INVALID); + contract_level level = mode.get_level (); + contract_role *role = mode.get_role (); + if (level == CONTRACT_DEFAULT) + return role->default_semantic; + else if (level == CONTRACT_AUDIT) + return role->audit_semantic; + else if (level == CONTRACT_AXIOM) + return role->axiom_semantic; + } + gcc_assert (false); +} + +/* Return true if any contract in CONTRACT_ATTRs is not yet parsed. */ + +bool +contract_any_deferred_p (tree contract_attr) +{ + for (; contract_attr; contract_attr = CONTRACT_CHAIN (contract_attr)) + if (CONTRACT_CONDITION_DEFERRED_P (CONTRACT_STATEMENT (contract_attr))) + return true; + return false; +} + +/* Returns true if all attributes are contracts. */ + +bool +all_attributes_are_contracts_p (tree attributes) +{ + for (; attributes; attributes = TREE_CHAIN (attributes)) + if (!cxx_contract_attribute_p (attributes)) + return false; + return true; +} + +/* Mark most of a contract as being invalid. */ + +tree +invalidate_contract (tree t) +{ + if (TREE_CODE (t) == POSTCONDITION_STMT && POSTCONDITION_IDENTIFIER (t)) + POSTCONDITION_IDENTIFIER (t) = error_mark_node; + CONTRACT_CONDITION (t) = error_mark_node; + CONTRACT_COMMENT (t) = error_mark_node; + return t; +} + +/* Returns an invented parameter declration of the form 'TYPE ID' for the + purpose of parsing the postcondition. + + We use a PARM_DECL instead of a VAR_DECL so that tsubst forces a lookup + in local specializations when we instantiate these things later. */ + +tree +make_postcondition_variable (cp_expr id, tree type) +{ + if (id == error_mark_node) + return id; + + tree decl = build_lang_decl (PARM_DECL, id, type); + DECL_ARTIFICIAL (decl) = true; + DECL_SOURCE_LOCATION (decl) = id.get_location (); + + pushdecl (decl); + return decl; +} + +/* As above, except that the type is unknown. */ + +tree +make_postcondition_variable (cp_expr id) +{ + return make_postcondition_variable (id, make_auto ()); +} + +/* Check that the TYPE is valid for a named postcondition variable. Emit a + diagnostic if it is not. Returns TRUE if the result is OK and false + otherwise. */ + +bool +check_postcondition_result (tree decl, tree type, location_t loc) +{ + if (VOID_TYPE_P (type)) + { + const char* what; + if (DECL_CONSTRUCTOR_P (decl)) + what = "constructor"; + else if (DECL_DESTRUCTOR_P (decl)) + what = "destructor"; + else + what = "function"; + error_at (loc, "%s does not return a value to test", what); + return false; + } + + return true; +} + +/* Instantiate each postcondition with the return type to finalize the + attribute. */ + +void +rebuild_postconditions (tree decl) +{ + tree type = TREE_TYPE (TREE_TYPE (decl)); + tree attributes = DECL_CONTRACTS (decl); + + for (; attributes ; attributes = TREE_CHAIN (attributes)) + { + if (!cxx_contract_attribute_p (attributes)) + continue; + tree contract = TREE_VALUE (TREE_VALUE (attributes)); + if (TREE_CODE (contract) != POSTCONDITION_STMT) + continue; + tree condition = CONTRACT_CONDITION (contract); + + /* If any conditions are deferred, they're all deferred. Note that + we don't have to instantiate postconditions in that case because + the type is available through the declaration. */ + if (TREE_CODE (condition) == DEFERRED_PARSE) + return; + + tree oldvar = POSTCONDITION_IDENTIFIER (contract); + if (!oldvar) + continue; + + /* Always update the context of the result variable so that it can + be remapped by remap_contracts. */ + DECL_CONTEXT (oldvar) = decl; + + /* If the return type is undeduced, defer until later. */ + if (TREE_CODE (type) == TEMPLATE_TYPE_PARM) + return; + + /* Check the postcondition variable. */ + location_t loc = DECL_SOURCE_LOCATION (oldvar); + if (!check_postcondition_result (decl, type, loc)) + { + invalidate_contract (contract); + continue; + } + + /* "Instantiate" the result variable using the known type. Also update + the context so the inliner will actually remap this the parameter when + generating contract checks. */ + tree newvar = copy_node (oldvar); + TREE_TYPE (newvar) = type; + + /* Make parameters and result available for substitution. */ + local_specialization_stack stack (lss_copy); + for (tree t = DECL_ARGUMENTS (decl); t != NULL_TREE; t = TREE_CHAIN (t)) + register_local_identity (t); + register_local_specialization (newvar, oldvar); + + ++processing_contract_condition; + condition = tsubst_expr (condition, make_tree_vec (0), + tf_warning_or_error, decl, false); + --processing_contract_condition; + + /* Update the contract condition and result. */ + POSTCONDITION_IDENTIFIER (contract) = newvar; + CONTRACT_CONDITION (contract) = finish_contract_condition (condition); + } +} + +static tree +build_comment (cp_expr condition) +{ + /* Try to get the actual source text for the condition; if that fails pretty + print the resulting tree. */ + char *str = get_source_text_between (condition.get_start (), + condition.get_finish ()); + if (!str) + { + /* FIXME cases where we end up here + #line macro usage (oof) + contracts10.C + contracts11.C */ + const char *str = expr_to_string (condition); + return build_string_literal (strlen (str) + 1, str); + } + + tree t = build_string_literal (strlen (str) + 1, str); + free (str); + return t; +} + +/* Build a contract statement. */ + +tree +grok_contract (tree attribute, tree mode, tree result, cp_expr condition, + location_t loc) +{ + tree_code code; + if (is_attribute_p ("assert", attribute)) + code = ASSERTION_STMT; + else if (is_attribute_p ("pre", attribute)) + code = PRECONDITION_STMT; + else if (is_attribute_p ("post", attribute)) + code = POSTCONDITION_STMT; + else + gcc_unreachable (); + + /* Build the contract. The condition is added later. In the case that + the contract is deferred, result an plain identifier, not a result + variable. */ + tree contract; + tree type = void_type_node; + if (code != POSTCONDITION_STMT) + contract = build3_loc (loc, code, type, mode, NULL_TREE, NULL_TREE); + else + contract = build4_loc (loc, code, type, mode, NULL_TREE, NULL_TREE, result); + + /* Determine the concrete semantic. */ + set_contract_semantic (contract, compute_concrete_semantic (contract)); + + /* If the contract is deferred, don't do anything with the condition. */ + if (TREE_CODE (condition) == DEFERRED_PARSE) + { + CONTRACT_CONDITION (contract) = condition; + return contract; + } + + /* Generate the comment from the original condition. */ + CONTRACT_COMMENT (contract) = build_comment (condition); + + /* The condition is converted to bool. */ + condition = finish_contract_condition (condition); + CONTRACT_CONDITION (contract) = condition; + + return contract; +} + +/* Build the contract attribute specifier where IDENTIFIER is one of 'pre', + 'post' or 'assert' and CONTRACT is the underlying statement. */ +tree +finish_contract_attribute (tree identifier, tree contract) +{ + if (contract == error_mark_node) + return error_mark_node; + + tree attribute = build_tree_list (build_tree_list (NULL_TREE, identifier), + build_tree_list (NULL_TREE, contract)); + + + /* Mark the attribute as dependent if the condition is dependent. + + TODO: I'm not sure this is strictly necessary. It's going to be marked as + such by a subroutine of cplus_decl_attributes. */ + tree condition = CONTRACT_CONDITION (contract); + if (TREE_CODE (condition) == DEFERRED_PARSE + || value_dependent_expression_p (condition)) + ATTR_IS_DEPENDENT (attribute) = true; + + return attribute; +} + +/* Update condition of a late-parsed contract and postcondition variable, + if any. */ + +void +update_late_contract (tree contract, tree result, tree condition) +{ + if (TREE_CODE (contract) == POSTCONDITION_STMT) + POSTCONDITION_IDENTIFIER (contract) = result; + + /* Generate the comment from the original condition. */ + CONTRACT_COMMENT (contract) = build_comment (condition); + + /* The condition is converted to bool. */ + condition = finish_contract_condition (condition); + CONTRACT_CONDITION (contract) = condition; +} + +/* Return TRUE iff ATTR has been parsed by the front-end as a c++2a contract + attribute. */ + +bool +cxx_contract_attribute_p (const_tree attr) +{ + if (attr == NULL_TREE + || TREE_CODE (attr) != TREE_LIST) + return false; + + if (!TREE_PURPOSE (attr) || TREE_CODE (TREE_PURPOSE (attr)) != TREE_LIST) + return false; + if (!TREE_VALUE (attr) || TREE_CODE (TREE_VALUE (attr)) != TREE_LIST) + return false; + if (!TREE_VALUE (TREE_VALUE (attr))) + return false; + + return (TREE_CODE (TREE_VALUE (TREE_VALUE (attr))) == PRECONDITION_STMT + || TREE_CODE (TREE_VALUE (TREE_VALUE (attr))) == POSTCONDITION_STMT + || TREE_CODE (TREE_VALUE (TREE_VALUE (attr))) == ASSERTION_STMT); +} + +/* True if ATTR is an assertion. */ + +bool +cp_contract_assertion_p (const_tree attr) +{ + /* This is only an assertion if it is a valid cxx contract attribute and the + statement is an ASSERTION_STMT. */ + return cxx_contract_attribute_p (attr) + && TREE_CODE (CONTRACT_STATEMENT (attr)) == ASSERTION_STMT; +} + +/* Remove all c++2a style contract attributes from the DECL_ATTRIBUTEs of the + FUNCTION_DECL FNDECL. */ + +void +remove_contract_attributes (tree fndecl) +{ + tree list = NULL_TREE; + for (tree p = DECL_ATTRIBUTES (fndecl); p; p = TREE_CHAIN (p)) + if (!cxx_contract_attribute_p (p)) + list = tree_cons (TREE_PURPOSE (p), TREE_VALUE (p), NULL_TREE); + DECL_ATTRIBUTES (fndecl) = nreverse (list); +} + +static tree find_first_non_contract (tree attributes) +{ + tree head = attributes; + tree p = find_contract (attributes); + + /* There are no contracts. */ + if (!p) + return head; + + /* There are leading contracts. */ + if (p == head) + { + while (cxx_contract_attribute_p (p)) + p = TREE_CHAIN (p); + head = p; + } + + return head; +} + +/* Remove contracts from ATTRIBUTES. */ + +tree splice_out_contracts (tree attributes) +{ + tree head = find_first_non_contract (attributes); + if (!head) + return NULL_TREE; + + /* Splice out remaining contracts. */ + tree p = TREE_CHAIN (head); + tree q = head; + while (p) + { + if (cxx_contract_attribute_p (p)) + { + /* Skip a sequence of contracts and then link q to the next + non-contract attribute. */ + do + p = TREE_CHAIN (p); + while (cxx_contract_attribute_p (p)); + TREE_CHAIN (q) = p; + } + else + p = TREE_CHAIN (p); + } + + return head; +} + +/* Copy contract attributes from NEWDECL onto the attribute list of OLDDECL. */ + +void copy_contract_attributes (tree olddecl, tree newdecl) +{ + tree attrs = NULL_TREE; + for (tree c = DECL_CONTRACTS (newdecl); c; c = TREE_CHAIN (c)) + { + if (!cxx_contract_attribute_p (c)) + continue; + attrs = tree_cons (TREE_PURPOSE (c), TREE_VALUE (c), attrs); + } + attrs = chainon (DECL_ATTRIBUTES (olddecl), nreverse (attrs)); + DECL_ATTRIBUTES (olddecl) = attrs; + + /* And update DECL_CONTEXT of the postcondition result identifier. */ + rebuild_postconditions (olddecl); +} + +/* Returns the parameter corresponding to the return value of a guarded + function D. Returns NULL_TREE if D has no postconditions or is void. */ + +static tree +get_postcondition_result_parameter (tree d) +{ + if (!d || d == error_mark_node) + return NULL_TREE; + + if (VOID_TYPE_P (TREE_TYPE (TREE_TYPE (d)))) + return NULL_TREE; + + tree post = DECL_POST_FN (d); + if (!post || post == error_mark_node) + return NULL_TREE; + + for (tree arg = DECL_ARGUMENTS (post); arg; arg = TREE_CHAIN (arg)) + if (!TREE_CHAIN (arg)) + return arg; + + return NULL_TREE; +} + + +/* For use with the tree inliner. This preserves non-mapped local variables, + such as postcondition result variables, during remapping. */ + +static tree +retain_decl (tree decl, copy_body_data *) +{ + return decl; +} + +/* Rewrite the condition of contract in place, so that references to SRC's + parameters are updated to refer to DST's parameters. The postcondition + result variable is left unchanged. + + This, along with remap_contracts, are subroutines of duplicate_decls. + When declarations are merged, we sometimes need to update contracts to + refer to new parameters. + + If DUPLICATE_P is true, this is called by duplicate_decls to rewrite contacts + in terms of a new set of parameters. In this case, we can retain local + variables appearing in the contract because the contract is not being + prepared for insertion into a new function. Importantly, this preserves the + references to postcondition results, which are not replaced during merging. + + If false, we're preparing to emit the contract condition into the body + of a new function, so we need to make copies of all local variables + appearing in the contract (e.g., if it includes a lambda expression). Note + that in this case, postcondition results are mapped to the last parameter + of DST. + + This is also used to reuse a parent type's contracts on virtual methods. */ + +static void +remap_contract (tree src, tree dst, tree contract, bool duplicate_p) +{ + copy_body_data id; + hash_map decl_map; + + memset (&id, 0, sizeof (id)); + id.src_fn = src; + id.dst_fn = dst; + id.src_cfun = DECL_STRUCT_FUNCTION (src); + id.decl_map = &decl_map; + + /* If we're merging contracts, don't copy local variables. */ + id.copy_decl = duplicate_p ? retain_decl : copy_decl_no_change; + + id.transform_call_graph_edges = CB_CGE_DUPLICATE; + id.transform_new_cfg = false; + id.transform_return_to_modify = false; + id.transform_parameter = true; + + /* Make sure not to unshare trees behind the front-end's back + since front-end specific mechanisms may rely on sharing. */ + id.regimplify = false; + id.do_not_unshare = true; + id.do_not_fold = true; + + /* We're not inside any EH region. */ + id.eh_lp_nr = 0; + + bool do_remap = false; + + /* Insert parameter remappings. */ + if (TREE_CODE (src) == FUNCTION_DECL) + src = DECL_ARGUMENTS (src); + if (TREE_CODE (dst) == FUNCTION_DECL) + dst = DECL_ARGUMENTS (dst); + + for (tree sp = src, dp = dst; + sp || dp; + sp = DECL_CHAIN (sp), dp = DECL_CHAIN (dp)) + { + if (!sp && dp + && TREE_CODE (contract) == POSTCONDITION_STMT + && DECL_CHAIN (dp) == NULL_TREE) + { + gcc_assert (!duplicate_p); + if (tree result = POSTCONDITION_IDENTIFIER (contract)) + { + gcc_assert (DECL_P (result)); + insert_decl_map (&id, result, dp); + do_remap = true; + } + break; + } + gcc_assert (sp && dp); + + if (sp == dp) + continue; + + insert_decl_map (&id, sp, dp); + do_remap = true; + } + if (!do_remap) + return; + + walk_tree (&CONTRACT_CONDITION (contract), copy_tree_body_r, &id, NULL); +} + +/* Rewrite any references to SRC's PARM_DECLs to the corresponding PARM_DECL in + DST in all of the contract attributes in CONTRACTS by calling remap_contract + on each. + + This is used for two purposes: to rewrite contract attributes during + duplicate_decls, and to prepare contracts for emission into a function's + respective precondition and postcondition functions. DUPLICATE_P is used + to determine the context in which this function is called. See above for + the behavior described by this flag. */ + +void +remap_contracts (tree src, tree dst, tree contracts, bool duplicate_p) +{ + for (tree attr = contracts; attr; attr = CONTRACT_CHAIN (attr)) + { + if (!cxx_contract_attribute_p (attr)) + continue; + tree contract = CONTRACT_STATEMENT (attr); + if (TREE_CODE (CONTRACT_CONDITION (contract)) != DEFERRED_PARSE) + remap_contract (src, dst, contract, duplicate_p); + } +} + +/* Helper to replace references to dummy this parameters with references to + the first argument of the FUNCTION_DECL DATA. */ + +static tree +remap_dummy_this_1 (tree *tp, int *, void *data) +{ + if (!is_this_parameter (*tp)) + return NULL_TREE; + tree fn = (tree)data; + *tp = DECL_ARGUMENTS (fn); + return NULL_TREE; +} + +/* Replace all references to dummy this parameters in EXPR with references to + the first argument of the FUNCTION_DECL FN. */ + +static void +remap_dummy_this (tree fn, tree *expr) +{ + walk_tree (expr, remap_dummy_this_1, fn, NULL); +} + +/* Contract matching. */ + +/* True if the contract is valid. */ + +static bool +contract_valid_p (tree contract) +{ + return CONTRACT_CONDITION (contract) != error_mark_node; +} + +/* True if the contract attribute is valid. */ + +static bool +contract_attribute_valid_p (tree attribute) +{ + return contract_valid_p (TREE_VALUE (TREE_VALUE (attribute))); +} + +/* Compare the contract conditions of OLD_ATTR and NEW_ATTR. Returns false + if the conditions are equivalent, and true otherwise. */ + +static bool +check_for_mismatched_contracts (tree old_attr, tree new_attr, + contract_matching_context ctx) +{ + tree old_contract = CONTRACT_STATEMENT (old_attr); + tree new_contract = CONTRACT_STATEMENT (new_attr); + + /* Different kinds of contracts do not match. */ + if (TREE_CODE (old_contract) != TREE_CODE (new_contract)) + { + auto_diagnostic_group d; + error_at (EXPR_LOCATION (new_contract), + ctx == cmc_declaration + ? "mismatched contract attribute in declaration" + : "mismatched contract attribute in override"); + inform (EXPR_LOCATION (old_contract), "previous contract here"); + return true; + } + + /* A deferred contract tentatively matches. */ + if (CONTRACT_CONDITION_DEFERRED_P (new_contract)) + return false; + + /* Compare the conditions of the contracts. We fold immediately to avoid + issues comparing contracts on overrides that use parameters -- see + contracts-pre3. */ + tree t1 = cp_fully_fold_init (CONTRACT_CONDITION (old_contract)); + tree t2 = cp_fully_fold_init (CONTRACT_CONDITION (new_contract)); + + /* Compare the contracts. The fold doesn't eliminate conversions to members. + Set the comparing_override_contracts flag to ensure that references + through 'this' are equal if they designate the same member, regardless of + the path those members. */ + bool saved_comparing_contracts = comparing_override_contracts; + comparing_override_contracts = (ctx == cmc_override); + bool matching_p = cp_tree_equal (t1, t2); + comparing_override_contracts = saved_comparing_contracts; + + if (!matching_p) + { + auto_diagnostic_group d; + error_at (EXPR_LOCATION (CONTRACT_CONDITION (new_contract)), + ctx == cmc_declaration + ? "mismatched contract condition in declaration" + : "mismatched contract condition in override"); + inform (EXPR_LOCATION (CONTRACT_CONDITION (old_contract)), + "previous contract here"); + return true; + } + + return false; +} + +/* Compare the contract attributes of OLDDECL and NEWDECL. Returns true + if the contracts match, and false if they differ. */ + +bool +match_contract_conditions (location_t oldloc, tree old_attrs, + location_t newloc, tree new_attrs, + contract_matching_context ctx) +{ + /* Contracts only match if they are both specified. */ + if (!old_attrs || !new_attrs) + return true; + + /* Compare each contract in turn. */ + while (old_attrs && new_attrs) + { + /* If either contract is ill-formed, skip the rest of the comparison, + since we've already diagnosed an error. */ + if (!contract_attribute_valid_p (new_attrs) + || !contract_attribute_valid_p (old_attrs)) + return false; + + if (check_for_mismatched_contracts (old_attrs, new_attrs, ctx)) + return false; + old_attrs = CONTRACT_CHAIN (old_attrs); + new_attrs = CONTRACT_CHAIN (new_attrs); + } + + /* If we didn't compare all attributes, the contracts don't match. */ + if (old_attrs || new_attrs) + { + auto_diagnostic_group d; + error_at (newloc, + ctx == cmc_declaration + ? "declaration has a different number of contracts than " + "previously declared" + : "override has a different number of contracts than " + "previously declared"); + inform (oldloc, + new_attrs + ? "original declaration with fewer contracts here" + : "original declaration with more contracts here"); + return false; + } + + return true; +} + +/* Deferred contract mapping. + + This is used to compare late-parsed contracts on overrides with their + base class functions. + + TODO: It seems like this could be replaced by a simple list that maps from + overrides to their base functions. It's not clear that we really need + a map to a function + a list of contracts. */ + +/* Map from FNDECL to a tree list of contracts that have not been matched or + diagnosed yet. The TREE_PURPOSE is the basefn we're overriding, and the + TREE_VALUE is the list of contract attrs for BASEFN. */ + +static hash_map pending_guarded_decls; + +void +defer_guarded_contract_match (tree fndecl, tree fn, tree contracts) +{ + if (!pending_guarded_decls.get (fndecl)) + { + pending_guarded_decls.put (fndecl, build_tree_list (fn, contracts)); + return; + } + for (tree pending = *pending_guarded_decls.get (fndecl); + pending; + pending = TREE_CHAIN (pending)) + { + if (TREE_VALUE (pending) == contracts) + return; + if (TREE_CHAIN (pending) == NULL_TREE) + TREE_CHAIN (pending) = build_tree_list (fn, contracts); + } +} + +/* If the FUNCTION_DECL DECL has any contracts that had their matching + deferred earlier, do that checking now. */ + +void +match_deferred_contracts (tree decl) +{ + tree *tp = pending_guarded_decls.get (decl); + if (!tp) + return; + + gcc_assert(!contract_any_deferred_p (DECL_CONTRACTS (decl))); + + processing_template_decl_sentinel ptds; + processing_template_decl = uses_template_parms (decl); + + /* Do late contract matching. */ + for (tree pending = *tp; pending; pending = TREE_CHAIN (pending)) + { + tree new_contracts = TREE_VALUE (pending); + location_t new_loc = CONTRACT_SOURCE_LOCATION (new_contracts); + tree old_contracts = DECL_CONTRACTS (decl); + location_t old_loc = CONTRACT_SOURCE_LOCATION (old_contracts); + tree base = TREE_PURPOSE (pending); + match_contract_conditions (new_loc, new_contracts, + old_loc, old_contracts, + base ? cmc_override : cmc_declaration); + } + + /* Clear out deferred match list so we don't check it twice. */ + pending_guarded_decls.remove (decl); +} + +/* Map from FUNCTION_DECL to a FUNCTION_DECL for either the PRE_FN or POST_FN. + These are used to parse contract conditions and are called inside the body + of the guarded function. */ +static GTY(()) hash_map *decl_pre_fn; +static GTY(()) hash_map *decl_post_fn; + +/* Returns the precondition funtion for D, or null if not set. */ + +tree +get_precondition_function (tree d) +{ + hash_map_maybe_create (decl_pre_fn); + tree *result = decl_pre_fn->get (d); + return result ? *result : NULL_TREE; +} + +/* Returns the postcondition funtion for D, or null if not set. */ + +tree +get_postcondition_function (tree d) +{ + hash_map_maybe_create (decl_post_fn); + tree *result = decl_post_fn->get (d); + return result ? *result : NULL_TREE; +} + +/* Makes PRE the precondition function for D. */ + +void +set_precondition_function (tree d, tree pre) +{ + gcc_assert (pre); + hash_map_maybe_create (decl_pre_fn); + gcc_assert (!decl_pre_fn->get (d)); + decl_pre_fn->put (d, pre); +} + +/* Makes POST the postcondition function for D. */ + +void +set_postcondition_function (tree d, tree post) +{ + gcc_assert (post); + hash_map_maybe_create (decl_post_fn); + gcc_assert (!decl_post_fn->get (d)); + decl_post_fn->put (d, post); +} + +/* Set the PRE and POST functions for D. Note that PRE and POST can be + null in this case. If so the functions are not recorded. */ + +void +set_contract_functions (tree d, tree pre, tree post) +{ + if (pre) + set_precondition_function (d, pre); + if (post) + set_postcondition_function (d, post); +} + +/* Return a copy of the FUNCTION_DECL IDECL with its own unshared + PARM_DECL and DECL_ATTRIBUTEs. */ + +static tree +copy_fn_decl (tree idecl) +{ + tree decl = copy_decl (idecl); + DECL_ATTRIBUTES (decl) = copy_list (DECL_ATTRIBUTES (idecl)); + + if (DECL_RESULT (idecl)) + { + DECL_RESULT (decl) = copy_decl (DECL_RESULT (idecl)); + DECL_CONTEXT (DECL_RESULT (decl)) = decl; + } + if (!DECL_ARGUMENTS (idecl) || VOID_TYPE_P (DECL_ARGUMENTS (idecl))) + return decl; + + tree last = DECL_ARGUMENTS (decl) = copy_decl (DECL_ARGUMENTS (decl)); + DECL_CONTEXT (last) = decl; + for (tree p = TREE_CHAIN (DECL_ARGUMENTS (idecl)); p; p = TREE_CHAIN (p)) + { + if (VOID_TYPE_P (p)) + { + TREE_CHAIN (last) = void_list_node; + break; + } + last = TREE_CHAIN (last) = copy_decl (p); + DECL_CONTEXT (last) = decl; + } + return decl; +} + +/* Build a declaration for the pre- or postcondition of a guarded FNDECL. */ + +static tree +build_contract_condition_function (tree fndecl, bool pre) +{ + if (TREE_TYPE (fndecl) == error_mark_node) + return error_mark_node; + if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fndecl) + && !TYPE_METHOD_BASETYPE (TREE_TYPE (fndecl))) + return error_mark_node; + + /* Create and rename the unchecked function and give an internal name. */ + tree fn = copy_fn_decl (fndecl); + DECL_RESULT (fn) = NULL_TREE; + tree value_type = pre ? void_type_node : TREE_TYPE (TREE_TYPE (fn)); + + /* Don't propagate declaration attributes to the checking function, + including the original contracts. */ + DECL_ATTRIBUTES (fn) = NULL_TREE; + + tree arg_types = NULL_TREE; + tree *last = &arg_types; + + /* FIXME will later optimizations delete unused args to prevent extra arg + passing? do we care? */ + tree class_type = NULL_TREE; + for (tree arg_type = TYPE_ARG_TYPES (TREE_TYPE (fn)); + arg_type && arg_type != void_list_node; + arg_type = TREE_CHAIN (arg_type)) + { + if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fndecl) + && TYPE_ARG_TYPES (TREE_TYPE (fn)) == arg_type) + { + class_type = TREE_TYPE (TREE_VALUE (arg_type)); + continue; + } + *last = build_tree_list (TREE_PURPOSE (arg_type), TREE_VALUE (arg_type)); + last = &TREE_CHAIN (*last); + } + + if (pre || VOID_TYPE_P (value_type)) + *last = void_list_node; + else + { + tree name = get_identifier ("__r"); + tree parm = build_lang_decl (PARM_DECL, name, value_type); + DECL_CONTEXT (parm) = fn; + DECL_ARGUMENTS (fn) = chainon (DECL_ARGUMENTS (fn), parm); + + *last = build_tree_list (NULL_TREE, value_type); + TREE_CHAIN (*last) = void_list_node; + + if (aggregate_value_p (value_type, fndecl)) + /* If FNDECL returns in memory, don't return the value from the + postcondition. */ + value_type = void_type_node; + } + + TREE_TYPE (fn) = build_function_type (value_type, arg_types); + if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fndecl)) + TREE_TYPE (fn) = build_method_type (class_type, TREE_TYPE (fn)); + + DECL_NAME (fn) = copy_node (DECL_NAME (fn)); + DECL_INITIAL (fn) = error_mark_node; + DECL_ABSTRACT_ORIGIN (fn) = fndecl; + + IDENTIFIER_VIRTUAL_P (DECL_NAME (fn)) = false; + DECL_VIRTUAL_P (fn) = false; + + /* Make these functions internal if we can, i.e. if the guarded function is + not vague linkage, or if we can put them in a comdat group with the + guarded function. */ + if (!DECL_WEAK (fndecl) || HAVE_COMDAT_GROUP) + { + TREE_PUBLIC (fn) = false; + DECL_EXTERNAL (fn) = false; + DECL_WEAK (fn) = false; + DECL_COMDAT (fn) = false; + + /* We haven't set the comdat group on the guarded function yet, we'll add + this to the same group in comdat_linkage later. */ + gcc_assert (!DECL_ONE_ONLY (fndecl)); + + DECL_INTERFACE_KNOWN (fn) = true; + } + + DECL_ARTIFICIAL (fn) = true; + + /* Update various inline related declaration properties. */ + //DECL_DECLARED_INLINE_P (fn) = true; + DECL_DISREGARD_INLINE_LIMITS (fn) = true; + TREE_NO_WARNING (fn) = 1; + + return fn; +} + +/* Return true if CONTRACT is checked or assumed under the current build + configuration. */ + +bool +contract_active_p (tree contract) +{ + return get_contract_semantic (contract) != CCS_IGNORE; +} + +static bool +has_active_contract_condition (tree d, tree_code c) +{ + for (tree as = DECL_CONTRACTS (d) ; as != NULL_TREE; as = TREE_CHAIN (as)) + { + tree contract = TREE_VALUE (TREE_VALUE (as)); + if (TREE_CODE (contract) == c && contract_active_p (contract)) + return true; + } + return false; +} + +/* True if D has any checked or assumed preconditions. */ + +static bool +has_active_preconditions (tree d) +{ + return has_active_contract_condition (d, PRECONDITION_STMT); +} + +/* True if D has any checked or assumed postconditions. */ + +static bool +has_active_postconditions (tree d) +{ + return has_active_contract_condition (d, POSTCONDITION_STMT); +} + +/* Return true if any contract in the CONTRACT list is checked or assumed + under the current build configuration. */ + +bool +contract_any_active_p (tree contract) +{ + for (; contract != NULL_TREE; contract = CONTRACT_CHAIN (contract)) + if (contract_active_p (TREE_VALUE (TREE_VALUE (contract)))) + return true; + return false; +} + +/* Do we need to mess with contracts for DECL1? */ + +static bool +handle_contracts_p (tree decl1) +{ + return (flag_contracts + && !processing_template_decl + && DECL_ABSTRACT_ORIGIN (decl1) == NULL_TREE + && contract_any_active_p (DECL_CONTRACTS (decl1))); +} + +/* Should we break out DECL1's pre/post contracts into separate functions? + FIXME I'd like this to default to 0, but that will need an overhaul to the + return identifier handling to just refor to the RESULT_DECL. */ + +static bool +outline_contracts_p (tree decl1) +{ + return (!DECL_CONSTRUCTOR_P (decl1) + && !DECL_DESTRUCTOR_P (decl1)); +} + +/* Build the precondition checking function for D. */ + +static tree +build_precondition_function (tree d) +{ + if (!has_active_preconditions (d)) + return NULL_TREE; + + return build_contract_condition_function (d, /*pre=*/true); +} + +/* Build the postcondition checking function for D. If the return + type is undeduced, don't build the function yet. We do that in + apply_deduced_return_type. */ + +static tree +build_postcondition_function (tree d) +{ + if (!has_active_postconditions (d)) + return NULL_TREE; + + tree type = TREE_TYPE (TREE_TYPE (d)); + if (is_auto (type)) + return NULL_TREE; + + return build_contract_condition_function (d, /*pre=*/false); +} + +static void +build_contract_function_decls (tree d) +{ + /* Constructors and destructors have their contracts inserted inline. */ + if (!outline_contracts_p (d)) + return; + + /* Build the pre/post functions (or not). */ + tree pre = build_precondition_function (d); + tree post = build_postcondition_function (d); + set_contract_functions (d, pre, post); +} + +static const char * +get_contract_level_name (tree contract) +{ + if (CONTRACT_LITERAL_MODE_P (contract)) + return ""; + if (tree mode = CONTRACT_MODE (contract)) + if (tree level = TREE_VALUE (mode)) + return IDENTIFIER_POINTER (level); + return "default"; +} + +static const char * +get_contract_role_name (tree contract) +{ + if (CONTRACT_LITERAL_MODE_P (contract)) + return ""; + if (tree mode = CONTRACT_MODE (contract)) + if (tree role = TREE_PURPOSE (mode)) + return IDENTIFIER_POINTER (role); + return "default"; +} + +/* Build a layout-compatible internal version of std::contract_violation. */ + +static tree +get_pseudo_contract_violation_type () +{ + if (!pseudo_contract_violation_type) + { + /* Must match : + class contract_violation { + const char* _M_file; + const char* _M_function; + const char* _M_comment; + const char* _M_level; + const char* _M_role; + uint_least32_t _M_line; + signed char _M_continue; + If this changes, also update the initializer in + build_contract_violation. */ + const tree types[] = { const_string_type_node, + const_string_type_node, + const_string_type_node, + const_string_type_node, + const_string_type_node, + uint_least32_type_node, + signed_char_type_node }; + tree fields = NULL_TREE; + for (tree type : types) + { + /* finish_builtin_struct wants fieldss chained in reverse. */ + tree next = build_decl (BUILTINS_LOCATION, FIELD_DECL, + NULL_TREE, type); + DECL_CHAIN (next) = fields; + fields = next; + } + iloc_sentinel ils (input_location); + input_location = BUILTINS_LOCATION; + pseudo_contract_violation_type = make_class_type (RECORD_TYPE); + finish_builtin_struct (pseudo_contract_violation_type, + "__pseudo_contract_violation", + fields, NULL_TREE); + CLASSTYPE_AS_BASE (pseudo_contract_violation_type) + = pseudo_contract_violation_type; + DECL_CONTEXT (TYPE_NAME (pseudo_contract_violation_type)) + = FROB_CONTEXT (global_namespace); + TREE_PUBLIC (TYPE_NAME (pseudo_contract_violation_type)) = true; + CLASSTYPE_LITERAL_P (pseudo_contract_violation_type) = true; + CLASSTYPE_LAZY_COPY_CTOR (pseudo_contract_violation_type) = true; + xref_basetypes (pseudo_contract_violation_type, /*bases=*/NULL_TREE); + pseudo_contract_violation_type + = cp_build_qualified_type (pseudo_contract_violation_type, + TYPE_QUAL_CONST); + } + return pseudo_contract_violation_type; +} + +/* Return a VAR_DECL to pass to handle_contract_violation. */ + +static tree +build_contract_violation (tree contract, contract_continuation cmode) +{ + expanded_location loc = expand_location (EXPR_LOCATION (contract)); + const char *function = fndecl_name (DECL_ORIGIN (current_function_decl)); + const char *level = get_contract_level_name (contract); + const char *role = get_contract_role_name (contract); + + /* Must match the type layout in get_pseudo_contract_violation_type. */ + tree ctor = build_constructor_va + (init_list_type_node, 7, + NULL_TREE, build_string_literal (loc.file), + NULL_TREE, build_string_literal (function), + NULL_TREE, CONTRACT_COMMENT (contract), + NULL_TREE, build_string_literal (level), + NULL_TREE, build_string_literal (role), + NULL_TREE, build_int_cst (uint_least32_type_node, loc.line), + NULL_TREE, build_int_cst (signed_char_type_node, cmode)); + + ctor = finish_compound_literal (get_pseudo_contract_violation_type (), + ctor, tf_none); + protected_set_expr_location (ctor, EXPR_LOCATION (contract)); + return ctor; +} + +/* Return handle_contract_violation(), declaring it if needed. */ + +static tree +declare_handle_contract_violation () +{ + tree fnname = get_identifier ("handle_contract_violation"); + tree viol_name = get_identifier ("contract_violation"); + tree l = lookup_qualified_name (global_namespace, fnname, + LOOK_want::HIDDEN_FRIEND); + for (tree f: lkp_range (l)) + if (TREE_CODE (f) == FUNCTION_DECL) + { + tree parms = TYPE_ARG_TYPES (TREE_TYPE (f)); + if (remaining_arguments (parms) != 1) + continue; + tree parmtype = non_reference (TREE_VALUE (parms)); + if (CLASS_TYPE_P (parmtype) + && TYPE_IDENTIFIER (parmtype) == viol_name) + return f; + } + + tree id_exp = get_identifier ("experimental"); + tree ns_exp = lookup_qualified_name (std_node, id_exp); + + tree violation = error_mark_node; + if (TREE_CODE (ns_exp) == NAMESPACE_DECL) + violation = lookup_qualified_name (ns_exp, viol_name, + LOOK_want::TYPE + |LOOK_want::HIDDEN_FRIEND); + + if (TREE_CODE (violation) == TYPE_DECL) + violation = TREE_TYPE (violation); + else + { + push_nested_namespace (std_node); + push_namespace (id_exp, /*inline*/false); + violation = make_class_type (RECORD_TYPE); + create_implicit_typedef (viol_name, violation); + DECL_SOURCE_LOCATION (TYPE_NAME (violation)) = BUILTINS_LOCATION; + DECL_CONTEXT (TYPE_NAME (violation)) = current_namespace; + pushdecl_namespace_level (TYPE_NAME (violation), /*hidden*/true); + pop_namespace (); + pop_nested_namespace (std_node); + } + + tree argtype = cp_build_qualified_type (violation, TYPE_QUAL_CONST); + argtype = cp_build_reference_type (argtype, /*rval*/false); + tree fntype = build_function_type_list (void_type_node, argtype, NULL_TREE); + + push_nested_namespace (global_namespace); + tree fn = build_cp_library_fn_ptr ("handle_contract_violation", fntype, + ECF_COLD); + pushdecl_namespace_level (fn, /*hiding*/true); + pop_nested_namespace (global_namespace); + + return fn; +} + +/* Build the call to handle_contract_violation for CONTRACT. */ + +static void +build_contract_handler_call (tree contract, + contract_continuation cmode) +{ + tree violation = build_contract_violation (contract, cmode); + tree violation_fn = declare_handle_contract_violation (); + tree call = build_call_n (violation_fn, 1, build_address (violation)); + finish_expr_stmt (call); +} + +/* Generate the code that checks or assumes a contract, but do not attach + it to the current context. This is called during genericization. */ + +tree +build_contract_check (tree contract) +{ + contract_semantic semantic = get_contract_semantic (contract); + if (semantic == CCS_INVALID) + return NULL_TREE; + + /* Ignored contracts are never checked or assumed. */ + if (semantic == CCS_IGNORE) + return void_node; + + remap_dummy_this (current_function_decl, &CONTRACT_CONDITION (contract)); + tree condition = CONTRACT_CONDITION (contract); + if (condition == error_mark_node) + return NULL_TREE; + + location_t loc = EXPR_LOCATION (contract); + + if (semantic == CCS_ASSUME) + return build_assume_call (loc, condition); + + tree if_stmt = begin_if_stmt (); + tree cond = build_x_unary_op (loc, + TRUTH_NOT_EXPR, + condition, NULL_TREE, + tf_warning_or_error); + finish_if_stmt_cond (cond, if_stmt); + + /* Get the continuation mode. */ + contract_continuation cmode; + switch (semantic) + { + case CCS_NEVER: cmode = NEVER_CONTINUE; break; + case CCS_MAYBE: cmode = MAYBE_CONTINUE; break; + default: gcc_unreachable (); + } + + build_contract_handler_call (contract, cmode); + if (cmode == NEVER_CONTINUE) + finish_expr_stmt (build_call_a (terminate_fn, 0, nullptr)); + + finish_then_clause (if_stmt); + tree scope = IF_SCOPE (if_stmt); + IF_SCOPE (if_stmt) = NULL; + return do_poplevel (scope); +} + +/* Add the contract statement CONTRACT to the current block if valid. */ + +static void +emit_contract_statement (tree contract) +{ + /* Only add valid contracts. */ + if (get_contract_semantic (contract) != CCS_INVALID + && CONTRACT_CONDITION (contract) != error_mark_node) + add_stmt (contract); +} + +/* Generate the statement for the given contract attribute by adding the + statement to the current block. Returns the next contract in the chain. */ + +static tree +emit_contract_attr (tree attr) +{ + gcc_assert (TREE_CODE (attr) == TREE_LIST); + + emit_contract_statement (CONTRACT_STATEMENT (attr)); + + return CONTRACT_CHAIN (attr); +} + +/* Add the statements of contract attributes ATTRS to the current block. */ + +static void +emit_contract_conditions (tree attrs, tree_code code) +{ + if (!attrs) return; + gcc_assert (TREE_CODE (attrs) == TREE_LIST); + gcc_assert (code == PRECONDITION_STMT || code == POSTCONDITION_STMT); + while (attrs) + { + tree contract = CONTRACT_STATEMENT (attrs); + if (TREE_CODE (contract) == code) + attrs = emit_contract_attr (attrs); + else + attrs = CONTRACT_CHAIN (attrs); + } +} + +/* Emit the statement for an assertion attribute. */ + +void +emit_assertion (tree attr) +{ + emit_contract_attr (attr); +} + +/* Emit statements for precondition attributes. */ + +static void +emit_preconditions (tree attr) +{ + return emit_contract_conditions (attr, PRECONDITION_STMT); +} + +/* Emit statements for postcondition attributes. */ + +static void +emit_postconditions_cleanup (tree contracts) +{ + tree stmts = push_stmt_list (); + emit_contract_conditions (contracts, POSTCONDITION_STMT); + stmts = pop_stmt_list (stmts); + push_cleanup (NULL_TREE, stmts, /*eh_only*/false); +} + +/* We're compiling the pre/postcondition function CONDFN; remap any FN + attributes that match CODE and emit them. */ + +static void +remap_and_emit_conditions (tree fn, tree condfn, tree_code code) +{ + gcc_assert (code == PRECONDITION_STMT || code == POSTCONDITION_STMT); + for (tree attr = DECL_CONTRACTS (fn); attr; + attr = CONTRACT_CHAIN (attr)) + { + tree contract = CONTRACT_STATEMENT (attr); + if (TREE_CODE (contract) == code) + { + contract = copy_node (contract); + remap_contract (fn, condfn, contract, /*duplicate_p=*/false); + emit_contract_statement (contract); + } + } +} + +/* Converts a contract condition to bool and ensures it has a locaiton. */ + +tree +finish_contract_condition (cp_expr condition) +{ + /* Ensure we have the condition location saved in case we later need to + emit a conversion error during template instantiation and wouldn't + otherwise have it. */ + if (!CAN_HAVE_LOCATION_P (condition) || EXCEPTIONAL_CLASS_P (condition)) + { + condition = build1_loc (condition.get_location (), VIEW_CONVERT_EXPR, + TREE_TYPE (condition), condition); + EXPR_LOCATION_WRAPPER_P (condition) = 1; + } + + if (condition == error_mark_node || type_dependent_expression_p (condition)) + return condition; + + return condition_conversion (condition); +} + +void +maybe_update_postconditions (tree fco) +{ + /* Update any postconditions and the postcondition checking function + as needed. If there are postconditions, we'll use those to rewrite + return statements to check postconditions. */ + if (has_active_postconditions (fco)) + { + rebuild_postconditions (fco); + tree post = build_postcondition_function (fco); + set_postcondition_function (fco, post); + } +} + +/* Called on attribute lists that must not contain contracts. If any + contracts are present, issue an error diagnostic and return true. */ + +bool +diagnose_misapplied_contracts (tree attributes) +{ + if (attributes == NULL_TREE) + return false; + + tree contract_attr = find_contract (attributes); + if (!contract_attr) + return false; + + error_at (EXPR_LOCATION (CONTRACT_STATEMENT (contract_attr)), + "contracts must appertain to a function type"); + + /* Invalidate the contract so we don't treat it as valid later on. */ + invalidate_contract (TREE_VALUE (TREE_VALUE (contract_attr))); + + return true; +} + +/* Build and return an argument list containing all the parameters of the + (presumably guarded) FUNCTION_DECL FN. This can be used to forward all of + FN's arguments to a function taking the same list of arguments -- namely + the unchecked form of FN. + + We use CALL_FROM_THUNK_P instead of forward_parm for forwarding + semantics. */ + +static vec * +build_arg_list (tree fn) +{ + vec *args = make_tree_vector (); + for (tree t = DECL_ARGUMENTS (fn); t; t = DECL_CHAIN (t)) + vec_safe_push (args, t); + return args; +} + +void +start_function_contracts (tree decl1) +{ + if (!handle_contracts_p (decl1)) + return; + + if (!outline_contracts_p (decl1)) + { + emit_preconditions (DECL_CONTRACTS (current_function_decl)); + emit_postconditions_cleanup (DECL_CONTRACTS (current_function_decl)); + return; + } + + /* Contracts may have just been added without a chance to parse them, though + we still need the PRE_FN available to generate a call to it. */ + if (!DECL_PRE_FN (decl1)) + build_contract_function_decls (decl1); + + /* If we're starting a guarded function with valid contracts, we need to + insert a call to the pre function. */ + if (DECL_PRE_FN (decl1) + && DECL_PRE_FN (decl1) != error_mark_node) + { + releasing_vec args = build_arg_list (decl1); + tree call = build_call_a (DECL_PRE_FN (decl1), + args->length (), + args->address ()); + CALL_FROM_THUNK_P (call) = true; + finish_expr_stmt (call); + } +} + +/* Finish up the pre & post function definitions for a guarded FNDECL, + and compile those functions all the way to assembler language output. */ + +void +finish_function_contracts (tree fndecl) +{ + if (!handle_contracts_p (fndecl) + || !outline_contracts_p (fndecl)) + return; + + for (tree ca = DECL_CONTRACTS (fndecl); ca; ca = CONTRACT_CHAIN (ca)) + { + tree contract = CONTRACT_STATEMENT (ca); + if (!CONTRACT_CONDITION (contract) + || CONTRACT_CONDITION_DEFERRED_P (contract) + || CONTRACT_CONDITION (contract) == error_mark_node) + return; + } + + int flags = SF_DEFAULT | SF_PRE_PARSED; + + /* If either the pre or post functions are bad, don't bother emitting + any contracts. The program is already ill-formed. */ + tree pre = DECL_PRE_FN (fndecl); + tree post = DECL_POST_FN (fndecl); + if (pre == error_mark_node || post == error_mark_node) + return; + + if (pre && DECL_INITIAL (fndecl) != error_mark_node) + { + DECL_PENDING_INLINE_P (pre) = false; + start_preparsed_function (pre, DECL_ATTRIBUTES (pre), flags); + remap_and_emit_conditions (fndecl, pre, PRECONDITION_STMT); + tree finished_pre = finish_function (false); + expand_or_defer_fn (finished_pre); + } + + if (post && DECL_INITIAL (fndecl) != error_mark_node) + { + DECL_PENDING_INLINE_P (post) = false; + start_preparsed_function (post, + DECL_ATTRIBUTES (post), + flags); + remap_and_emit_conditions (fndecl, post, POSTCONDITION_STMT); + if (!VOID_TYPE_P (TREE_TYPE (TREE_TYPE (post)))) + finish_return_stmt (get_postcondition_result_parameter (fndecl)); + + tree finished_post = finish_function (false); + expand_or_defer_fn (finished_post); + } +} + +/* Rewrite the expression of a returned expression so that it invokes the + postcondition function as needed. */ + +tree +apply_postcondition_to_return (tree expr) +{ + tree fn = current_function_decl; + tree post = DECL_POST_FN (fn); + if (!post) + return NULL_TREE; + + /* If FN returns in memory, POST has a void return type and we call it when + EXPR is DECL_RESULT (fn). If FN returns a scalar, POST has the same + return type and we call it when EXPR is the value being returned. */ + if (VOID_TYPE_P (TREE_TYPE (TREE_TYPE (post))) + != (expr == DECL_RESULT (fn))) + return NULL_TREE; + + releasing_vec args = build_arg_list (fn); + if (get_postcondition_result_parameter (fn)) + vec_safe_push (args, expr); + tree call = build_call_a (post, + args->length (), + args->address ()); + CALL_FROM_THUNK_P (call) = true; + + return call; +} + +/* A subroutine of duplicate_decls. Diagnose issues in the redeclaration of + guarded functions. */ + +void +duplicate_contracts (tree newdecl, tree olddecl) +{ + if (TREE_CODE (newdecl) == TEMPLATE_DECL) + newdecl = DECL_TEMPLATE_RESULT (newdecl); + if (TREE_CODE (olddecl) == TEMPLATE_DECL) + olddecl = DECL_TEMPLATE_RESULT (olddecl); + + /* Compare contracts to see if they match. */ + tree old_contracts = DECL_CONTRACTS (olddecl); + tree new_contracts = DECL_CONTRACTS (newdecl); + + if (!old_contracts && !new_contracts) + return; + + location_t old_loc = DECL_SOURCE_LOCATION (olddecl); + location_t new_loc = DECL_SOURCE_LOCATION (newdecl); + + /* If both declarations specify contracts, ensure they match. + + TODO: This handles a potential error a little oddly. Consider: + + struct B { + virtual void f(int n) [[pre: n == 0]]; + }; + struct D : B { + void f(int n) override; // inherits contracts + }; + void D::f(int n) [[pre: n == 0]] // OK + { } + + It's okay because we're explicitly restating the inherited contract. + Changing the precondition on the definition D::f causes match_contracts + to complain about the mismatch. + + This would previously have been diagnosed as adding contracts to an + override, but this seems like it should be well-formed. */ + if (old_contracts && new_contracts) + { + if (!match_contract_conditions (old_loc, old_contracts, + new_loc, new_contracts, + cmc_declaration)) + return; + if (DECL_UNIQUE_FRIEND_P (newdecl)) + /* Newdecl's contracts are still DEFERRED_PARSE, and we're about to + collapse it into olddecl, so stash away olddecl's contracts for + later comparison. */ + defer_guarded_contract_match (olddecl, olddecl, old_contracts); + } + + /* Handle cases where contracts are omitted in one or the other + declaration. */ + if (old_contracts) + { + /* Contracts have been previously specified by are no omitted. The + new declaration inherits the existing contracts. */ + if (!new_contracts) + copy_contract_attributes (newdecl, olddecl); + + /* In all cases, remove existing contracts from OLDDECL to prevent the + attribute merging function from adding excess contracts. */ + remove_contract_attributes (olddecl); + } + else if (!old_contracts) + { + /* We are adding contracts to a declaration. */ + if (new_contracts) + { + /* We can't add to a previously defined function. */ + if (DECL_INITIAL (olddecl)) + { + auto_diagnostic_group d; + error_at (new_loc, "cannot add contracts after definition"); + inform (DECL_SOURCE_LOCATION (olddecl), "original definition here"); + return; + } + + /* We can't add to an unguarded virtual function declaration. */ + if (DECL_VIRTUAL_P (olddecl) && new_contracts) + { + auto_diagnostic_group d; + error_at (new_loc, "cannot add contracts to a virtual function"); + inform (DECL_SOURCE_LOCATION (olddecl), "original declaration here"); + return; + } + + /* Depending on the "first declaration" rule, we may not be able + to add contracts to a function after the fact. */ + if (flag_contract_strict_declarations) + { + warning_at (new_loc, + OPT_fcontract_strict_declarations_, + "declaration adds contracts to %q#D", + olddecl); + return; + } + + /* Copy the contracts from NEWDECL to OLDDECL. We shouldn't need to + remap them because NEWDECL's parameters will replace those of + OLDDECL. Remove the contracts from NEWDECL so they aren't + cloned when merging. */ + copy_contract_attributes (olddecl, newdecl); + remove_contract_attributes (newdecl); + } + } +} + +/* Replace the any contract attributes on OVERRIDER with a copy where any + references to BASEFN's PARM_DECLs have been rewritten to the corresponding + PARM_DECL in OVERRIDER. */ + +void +inherit_base_contracts (tree overrider, tree basefn) +{ + tree last = NULL_TREE, contract_attrs = NULL_TREE; + for (tree a = DECL_CONTRACTS (basefn); + a != NULL_TREE; + a = CONTRACT_CHAIN (a)) + { + tree c = copy_node (a); + TREE_VALUE (c) = build_tree_list (TREE_PURPOSE (TREE_VALUE (c)), + copy_node (CONTRACT_STATEMENT (c))); + + tree src = basefn; + tree dst = overrider; + remap_contract (src, dst, CONTRACT_STATEMENT (c), /*duplicate_p=*/true); + + CONTRACT_COMMENT (CONTRACT_STATEMENT (c)) = + copy_node (CONTRACT_COMMENT (CONTRACT_STATEMENT (c))); + + chainon (last, c); + last = c; + if (!contract_attrs) + contract_attrs = c; + } + + set_decl_contracts (overrider, contract_attrs); +} + +#include "gt-cp-contracts.h" diff --git a/gcc/cp/cp-gimplify.cc b/gcc/cp/cp-gimplify.cc index cc8bfada5af..983f2a566a6 100644 --- a/gcc/cp/cp-gimplify.cc +++ b/gcc/cp/cp-gimplify.cc @@ -1427,6 +1427,23 @@ cp_genericize_r (tree *stmt_p, int *walk_subtrees, void *data) wtd->bind_expr_stack.pop (); break; + case ASSERTION_STMT: + case PRECONDITION_STMT: + case POSTCONDITION_STMT: + { + if (tree check = build_contract_check (stmt)) + { + *stmt_p = check; + return cp_genericize_r (stmt_p, walk_subtrees, data); + } + + /* If we didn't build a check, replace it with void_node so we don't + leak contracts into GENERIC. */ + *stmt_p = void_node; + *walk_subtrees = 0; + } + break; + case USING_STMT: { tree block = NULL_TREE; diff --git a/gcc/cp/cp-objcp-common.cc b/gcc/cp/cp-objcp-common.cc index e4df30d9720..1a682abcc4f 100644 --- a/gcc/cp/cp-objcp-common.cc +++ b/gcc/cp/cp-objcp-common.cc @@ -25,6 +25,7 @@ along with GCC; see the file COPYING3. If not see #include "cp-objcp-common.h" #include "dwarf2.h" #include "stringpool.h" +#include "contracts.h" /* Special routine to get the alias set for C++. */ @@ -86,6 +87,9 @@ cp_tree_size (enum tree_code code) case CONSTRAINT_INFO: return sizeof (tree_constraint_info); case USERDEF_LITERAL: return sizeof (tree_userdef_literal); case TEMPLATE_DECL: return sizeof (tree_template_decl); + case ASSERTION_STMT: return sizeof (tree_exp); + case PRECONDITION_STMT: return sizeof (tree_exp); + case POSTCONDITION_STMT: return sizeof (tree_exp); default: switch (TREE_CODE_CLASS (code)) { @@ -565,6 +569,10 @@ cp_common_init_ts (void) MARK_TS_EXP (CO_YIELD_EXPR); MARK_TS_EXP (CO_RETURN_EXPR); + MARK_TS_EXP (ASSERTION_STMT); + MARK_TS_EXP (PRECONDITION_STMT); + MARK_TS_EXP (POSTCONDITION_STMT); + c_common_init_ts (); } @@ -577,6 +585,39 @@ cp_handle_option (size_t scode, const char *arg, HOST_WIDE_INT value, { if (handle_module_option (unsigned (scode), arg, value)) return true; + + enum opt_code code = (enum opt_code) scode; + bool handled_p = true; + + switch (code) + { + case OPT_fcontract_build_level_: + handle_OPT_fcontract_build_level_ (arg); + break; + + case OPT_fcontract_assumption_mode_: + handle_OPT_fcontract_assumption_mode_ (arg); + break; + + case OPT_fcontract_continuation_mode_: + handle_OPT_fcontract_continuation_mode_ (arg); + break; + + case OPT_fcontract_role_: + handle_OPT_fcontract_role_ (arg); + break; + + case OPT_fcontract_semantic_: + handle_OPT_fcontract_semantic_ (arg); + break; + + default: + handled_p = false; + break; + } + if (handled_p) + return handled_p; + return c_common_handle_option (scode, arg, value, kind, loc, handlers); } diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc index 6e98ea35a39..f4d6ee5dede 100644 --- a/gcc/cp/decl.cc +++ b/gcc/cp/decl.cc @@ -2202,6 +2202,8 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden) = DECL_OVERLOADED_OPERATOR_CODE_RAW (olddecl); new_defines_function = DECL_INITIAL (newdecl) != NULL_TREE; + duplicate_contracts (newdecl, olddecl); + /* Optionally warn about more than one declaration for the same name, but don't warn about a function declaration followed by a definition. */ @@ -2275,6 +2277,13 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden) specializations. */ gcc_assert (!DECL_TEMPLATE_SPECIALIZATIONS (newdecl)); + /* Make sure the contracts are equivalent. */ + duplicate_contracts (newdecl, olddecl); + + /* Remove contracts from old_result so they aren't appended to + old_result by the merge function. */ + remove_contract_attributes (old_result); + DECL_ATTRIBUTES (old_result) = (*targetm.merge_decl_attributes) (old_result, new_result); @@ -2797,11 +2806,23 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden) } if (! types_match || new_defines_function) { + /* These are the final DECL_ARGUMENTS that will be used within the + body; update any references to old DECL_ARGUMENTS in the + contracts, if present. */ + if (tree contracts = DECL_CONTRACTS (newdecl)) + remap_contracts (olddecl, newdecl, contracts, true); + /* These need to be copied so that the names are available. Note that if the types do match, we'll preserve inline info and other bits, but if not, we won't. */ DECL_ARGUMENTS (olddecl) = DECL_ARGUMENTS (newdecl); DECL_RESULT (olddecl) = DECL_RESULT (newdecl); + + /* In some cases, duplicate_contracts will remove contracts from + OLDDECL, to avoid duplications. Sometimes, the contracts end up + shared. If we removed them, re-add them. */ + if (!DECL_CONTRACTS (olddecl)) + copy_contract_attributes (olddecl, newdecl); } /* If redeclaring a builtin function, it stays built in if newdecl is a gnu_inline definition, or if newdecl is just @@ -2845,7 +2866,38 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden) /* Don't clear out the arguments if we're just redeclaring a function. */ if (DECL_ARGUMENTS (olddecl)) - DECL_ARGUMENTS (newdecl) = DECL_ARGUMENTS (olddecl); + { + /* If we removed contracts from previous definition, re-attach + them. Otherwise, rewrite the contracts so they match the + parameters of the new declaration. */ + if (DECL_INITIAL (olddecl) + && DECL_CONTRACTS (newdecl) + && !DECL_CONTRACTS (olddecl)) + copy_contract_attributes (olddecl, newdecl); + else + { + /* Temporarily undo the re-contexting of parameters so we can + actually remap parameters. The inliner won't replace + parameters if we don't do this. */ + tree args = DECL_ARGUMENTS (newdecl); + for (tree p = args; p; p = DECL_CHAIN (p)) + DECL_CONTEXT (p) = newdecl; + + /* Save new argument names for use in contracts parsing, + unless we've already started parsing the body of olddecl + (particular issues arise when newdecl is from a prior + friend decl with no argument names, see + modules/contracts-tpl-friend-1). */ + if (tree contracts = DECL_CONTRACTS (olddecl)) + remap_contracts (newdecl, olddecl, contracts, true); + + /* And reverse this operation again. */ + for (tree p = args; p; p = DECL_CHAIN (p)) + DECL_CONTEXT (p) = olddecl; + } + + DECL_ARGUMENTS (newdecl) = DECL_ARGUMENTS (olddecl); + } } } else if (TREE_CODE (newdecl) == NAMESPACE_DECL) @@ -5456,6 +5508,12 @@ check_tag_decl (cp_decl_specifier_seq *declspecs, warn_misplaced_attr_for_class_type (loc, declared_type); } + /* Diagnose invalid application of contracts, if any. */ + if (find_contract (declspecs->attributes)) + diagnose_misapplied_contracts (declspecs->attributes); + else + diagnose_misapplied_contracts (declspecs->std_attributes); + return declared_type; } @@ -5747,9 +5805,16 @@ start_decl (const cp_declarator *declarator, if (DECL_EXTERNAL (decl) && ! DECL_TEMPLATE_SPECIALIZATION (decl) /* Aliases are definitions. */ && !alias) - permerror (declarator->id_loc, - "declaration of %q#D outside of class is not definition", - decl); + { + if (DECL_VIRTUAL_P (decl) || !flag_contracts) + permerror (declarator->id_loc, + "declaration of %q#D outside of class is not definition", + decl); + else if (flag_contract_strict_declarations) + warning_at (declarator->id_loc, OPT_fcontract_strict_declarations_, + "declaration of %q#D outside of class is not definition", + decl); + } } /* Create a DECL_LANG_SPECIFIC so that DECL_DECOMPOSITION_P works. */ @@ -10531,6 +10596,9 @@ grokfndecl (tree ctype, *attrlist = NULL_TREE; } + if (DECL_HAS_CONTRACTS_P (decl)) + rebuild_postconditions (decl); + /* Check main's type after attributes have been applied. */ if (ctype == NULL_TREE && DECL_MAIN_P (decl)) { @@ -12731,7 +12799,8 @@ grokdeclarator (const cp_declarator *declarator, } } - if (declspecs->std_attributes) + if (declspecs->std_attributes + && !diagnose_misapplied_contracts (declspecs->std_attributes)) { location_t attr_loc = declspecs->locations[ds_std_attribute]; if (warning_at (attr_loc, OPT_Wattributes, "attribute ignored")) @@ -12739,6 +12808,9 @@ grokdeclarator (const cp_declarator *declarator, "is ignored"); } + if (attrlist) + diagnose_misapplied_contracts (*attrlist); + /* Determine the type of the entity declared by recurring on the declarator. */ for (; declarator; declarator = declarator->declarator) @@ -12776,6 +12848,12 @@ grokdeclarator (const cp_declarator *declarator, inner_declarator = declarator->declarator; + /* Check that contracts aren't misapplied. */ + if (tree contract_attr = find_contract (declarator->std_attributes)) + if (declarator->kind != cdk_function + || innermost_code != cdk_function) + diagnose_misapplied_contracts (contract_attr); + /* We don't want to warn in parameter context because we don't yet know if the parse will succeed, and this might turn out to be a constructor call. */ @@ -13168,6 +13246,23 @@ grokdeclarator (const cp_declarator *declarator, else returned_attrs = attr_chainon (returned_attrs, att); } + + /* Actually apply the contract attributes to the declaration. */ + for (tree *p = &attrs; *p;) + { + tree l = *p; + if (cxx_contract_attribute_p (l)) + { + *p = TREE_CHAIN (l); + /* Intentionally reverse order of contracts so they're + reversed back into their lexical order. */ + TREE_CHAIN (l) = NULL_TREE; + returned_attrs = chainon (l, returned_attrs); + } + else + p = &TREE_CHAIN (l); + } + if (attrs) /* [dcl.fct]/2: @@ -14190,7 +14285,10 @@ grokdeclarator (const cp_declarator *declarator, { /* Packages tend to use GNU attributes on friends, so we only warn for standard attributes. */ - if (attrlist && !funcdef_flag && cxx11_attribute_p (*attrlist)) + if (attrlist + && !funcdef_flag + && cxx11_attribute_p (*attrlist) + && !all_attributes_are_contracts_p (*attrlist)) { *attrlist = NULL_TREE; if (warning_at (id_loc, OPT_Wattributes, "attribute ignored")) @@ -17410,6 +17508,8 @@ start_preparsed_function (tree decl1, tree attrs, int flags) store_parm_decls (current_function_parms); + start_function_contracts (decl1); + if (!processing_template_decl && (flag_lifetime_dse > 1) && DECL_CONSTRUCTOR_P (decl1) @@ -18130,6 +18230,9 @@ finish_function (bool inline_p) current_function_decl = NULL_TREE; invoke_plugin_callbacks (PLUGIN_FINISH_PARSE_FUNCTION, fndecl); + + finish_function_contracts (fndecl); + return fndecl; } diff --git a/gcc/cp/decl2.cc b/gcc/cp/decl2.cc index eeb59eae64f..f95529a5c9a 100644 --- a/gcc/cp/decl2.cc +++ b/gcc/cp/decl2.cc @@ -1561,6 +1561,9 @@ cp_check_const_attributes (tree attributes) tree attr; for (attr = attributes; attr; attr = TREE_CHAIN (attr)) { + if (cxx_contract_attribute_p (attr)) + continue; + tree arg; /* As we implement alignas using gnu::aligned attribute and alignas argument is a constant expression, force manifestly @@ -2106,7 +2109,17 @@ void comdat_linkage (tree decl) { if (flag_weak) - make_decl_one_only (decl, cxx_comdat_group (decl)); + { + make_decl_one_only (decl, cxx_comdat_group (decl)); + if (HAVE_COMDAT_GROUP && flag_contracts && DECL_CONTRACTS (decl)) + { + symtab_node *n = symtab_node::get (decl); + if (tree pre = DECL_PRE_FN (decl)) + cgraph_node::get_create (pre)->add_to_same_comdat_group (n); + if (tree post = DECL_POST_FN (decl)) + cgraph_node::get_create (post)->add_to_same_comdat_group (n); + } + } else if (TREE_CODE (decl) == FUNCTION_DECL || (VAR_P (decl) && DECL_ARTIFICIAL (decl))) /* We can just emit function and compiler-generated variables diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc index da8c95c9b43..12b28e8ee5b 100644 --- a/gcc/cp/error.cc +++ b/gcc/cp/error.cc @@ -567,7 +567,8 @@ dump_type (cxx_pretty_printer *pp, tree t, int flags) else { pp_cxx_cv_qualifier_seq (pp, t); - pp_cxx_tree_identifier (pp, TYPE_IDENTIFIER (t)); + if (tree id = TYPE_IDENTIFIER (t)) + pp_cxx_tree_identifier (pp, id); } break; diff --git a/gcc/cp/g++spec.cc b/gcc/cp/g++spec.cc index b63d8350ba1..257e49b7f3f 100644 --- a/gcc/cp/g++spec.cc +++ b/gcc/cp/g++spec.cc @@ -31,6 +31,8 @@ along with GCC; see the file COPYING3. If not see #define WITHLIBC (1<<3) /* Skip this option. */ #define SKIPOPT (1<<4) +/* Add -lstdc++exp for experimental features that need library support. */ +#define EXPERIMENTAL (1<<5) #ifndef MATH_LIBRARY #define MATH_LIBRARY "m" @@ -158,6 +160,11 @@ lang_specific_driver (struct cl_decoded_option **in_decoded_options, switch (decoded_options[i].opt_index) { + case OPT_fcontracts: + args[i] |= EXPERIMENTAL; + ++added; + break; + case OPT_nostdlib: case OPT_nostdlib__: case OPT_nodefaultlibs: @@ -348,6 +355,11 @@ lang_specific_driver (struct cl_decoded_option **in_decoded_options, &new_decoded_options[j]); } + if ((args[i] & EXPERIMENTAL) + && which_library == USE_LIBSTDCXX) + generate_option (OPT_l, "stdc++exp", 1, CL_DRIVER, + &new_decoded_options[++j]); + if ((args[i] & SKIPOPT) != 0) --j; diff --git a/gcc/cp/mangle.cc b/gcc/cp/mangle.cc index e97428e8f30..e363ef35b9f 100644 --- a/gcc/cp/mangle.cc +++ b/gcc/cp/mangle.cc @@ -856,6 +856,13 @@ write_encoding (const tree decl) mangle_return_type_p (decl), d); + /* If this is the pre/post function for a guarded function, append + .pre/post, like something from create_virtual_clone. */ + if (DECL_IS_PRE_FN_P (decl)) + write_string (".pre"); + else if (DECL_IS_POST_FN_P (decl)) + write_string (".post"); + /* If this is a coroutine helper, then append an appropriate string to identify which. */ if (tree ramp = DECL_RAMP_FN (decl)) diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc index 0e9af318ba4..a1764354ba5 100644 --- a/gcc/cp/module.cc +++ b/gcc/cp/module.cc @@ -10186,6 +10186,20 @@ trees_out::fn_parms_init (tree fn) base_tag - ix, ix, parm, fn); tree_node_vals (parm); } + + if (!streaming_p ()) + { + /* We must walk contract attrs so the dependency graph is complete. */ + for (tree contract = DECL_CONTRACTS (fn); + contract; + contract = CONTRACT_CHAIN (contract)) + tree_node (contract); + } + + /* Write a reference to contracts pre/post functions, if any, to avoid + regenerating them in importers. */ + tree_node (DECL_PRE_FN (fn)); + tree_node (DECL_POST_FN (fn)); } /* Build skeleton parm nodes, read their flags, type & parm indices. */ @@ -10220,6 +10234,11 @@ trees_in::fn_parms_init (tree fn) return 0; } + /* Reload references to contract functions, if any. */ + tree pre_fn = tree_node (); + tree post_fn = tree_node (); + set_contract_functions (fn, pre_fn, post_fn); + return base_tag; } @@ -10807,7 +10826,15 @@ check_mergeable_decl (merge_kind mk, tree decl, tree ovl, merge_key const &key) Matches decls_match behaviour. */ && (!DECL_IS_UNDECLARED_BUILTIN (m_inner) || !DECL_EXTERN_C_P (m_inner) - || DECL_EXTERN_C_P (d_inner))) + || DECL_EXTERN_C_P (d_inner)) + /* Reject if one is a different member of a + guarded/pre/post fn set. */ + && (!flag_contracts + || (DECL_IS_PRE_FN_P (d_inner) + == DECL_IS_PRE_FN_P (m_inner))) + && (!flag_contracts + || (DECL_IS_POST_FN_P (d_inner) + == DECL_IS_POST_FN_P (m_inner)))) { tree m_reqs = get_constraints (m_inner); if (m_reqs) @@ -14575,6 +14602,7 @@ module_state_config::get_dialect () cxx_dialect < cxx20 && flag_concepts ? "/concepts" : "", flag_coroutines ? "/coroutines" : "", flag_module_implicit_inline ? "/implicit-inline" : "", + flag_contracts ? "/contracts" : "", NULL); return dialect; diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc index 9523f73d9a3..e02b6229d90 100644 --- a/gcc/cp/parser.cc +++ b/gcc/cp/parser.cc @@ -46,6 +46,7 @@ along with GCC; see the file COPYING3. If not see #include "cp-name-hint.h" #include "memmodel.h" #include "c-family/known-headers.h" +#include "contracts.h" #include "bitmap.h" @@ -2192,11 +2193,14 @@ cp_parser_context_new (cp_parser_context* next) parser->unparsed_queues->last ().nsdmis #define unparsed_noexcepts \ parser->unparsed_queues->last ().noexcepts +#define unparsed_contracts \ + parser->unparsed_queues->last ().contracts static void push_unparsed_function_queues (cp_parser *parser) { - cp_unparsed_functions_entry e = { NULL, make_tree_vector (), NULL, NULL }; + cp_unparsed_functions_entry e + = { NULL, make_tree_vector (), NULL, NULL, NULL }; vec_safe_push (parser->unparsed_queues, e); } @@ -2695,6 +2699,10 @@ static tree cp_parser_transaction_cancel static tree cp_parser_yield_expression (cp_parser *); +/* Contracts */ + +static void cp_parser_late_contract_condition + (cp_parser *, tree, tree); enum pragma_context { pragma_external, @@ -2907,6 +2915,8 @@ static bool cp_parser_array_designator_p (cp_parser *); static bool cp_parser_init_statement_p (cp_parser *); +static bool cp_parser_skip_up_to_closing_square_bracket + (cp_parser *); static bool cp_parser_skip_to_closing_square_bracket (cp_parser *); static size_t cp_parser_skip_balanced_tokens (cp_parser *, size_t); @@ -12024,6 +12034,16 @@ cp_parser_handle_statement_omp_attributes (cp_parser *parser, tree attrs) return attrs; } +/* True if and only if the name is one of the contract types. */ + +static bool +contract_attribute_p (const_tree id) +{ + return is_attribute_p ("assert", id) + || is_attribute_p ("pre", id) + || is_attribute_p ("post", id); +} + /* Handle omp::directive and omp::sequence attributes in *PATTRS (if any) at the start or after declaration-id of a declaration. */ @@ -12157,7 +12177,10 @@ cp_parser_handle_directive_omp_attributes (cp_parser *parser, tree *pattrs, is a (possibly labeled) if statement which is not enclosed in braces and has an else clause. This is used to implement -Wparentheses. - CHAIN is a vector of if-else-if conditions. */ + CHAIN is a vector of if-else-if conditions. + + Note that this version of parsing restricts assertions to be attached to + empty statements. */ static void cp_parser_statement (cp_parser* parser, tree in_statement_expr, @@ -12199,6 +12222,23 @@ cp_parser_statement (cp_parser* parser, tree in_statement_expr, /* Peek at the next token. */ token = cp_lexer_peek_token (parser->lexer); + + /* If we have contracts, check that they're valid in this context. */ + if (std_attrs != error_mark_node) + { + if (tree pre = lookup_attribute ("pre", std_attrs)) + error_at (EXPR_LOCATION (TREE_VALUE (pre)), + "preconditions cannot be statements"); + else if (tree post = lookup_attribute ("post", std_attrs)) + error_at (EXPR_LOCATION (TREE_VALUE (post)), + "postconditions cannot be statements"); + + /* Check that assertions are null statements. */ + if (cp_contract_assertion_p (std_attrs)) + if (token->type != CPP_SEMICOLON) + error_at (token->location, "assertions must be followed by %<;%>"); + } + bool omp_attrs_forbidden_p; omp_attrs_forbidden_p = parser->omp_attrs_forbidden_p; @@ -12514,6 +12554,15 @@ cp_parser_statement (cp_parser* parser, tree in_statement_expr, "% attribute not followed by %<;%>"); std_attrs = NULL_TREE; } + + /* Handle [[assert: ...]]; */ + if (cp_contract_assertion_p (std_attrs)) + { + /* Add the assertion as a statement in the current block. */ + gcc_assert (!statement || statement == error_mark_node); + emit_assertion (std_attrs); + std_attrs = NULL_TREE; + } } /* Set the line number for the statement. */ @@ -15697,7 +15746,12 @@ cp_parser_decl_specifier_seq (cp_parser* parser, declared. */; else { - if (decl_specs->type && CLASS_TYPE_P (decl_specs->type)) + if (find_contract (attrs)) + { + diagnose_misapplied_contracts (attrs); + attrs = NULL_TREE; + } + else if (decl_specs->type && CLASS_TYPE_P (decl_specs->type)) { /* This is an attribute following a class-specifier. */ @@ -25398,11 +25452,11 @@ cp_parser_braced_list (cp_parser* parser, bool* non_constant_p) return result; } -/* Consume tokens up to, and including, the next non-nested closing `]'. +/* Consume tokens up to, but not including, the next non-nested closing `]'. Returns true iff we found a closing `]'. */ static bool -cp_parser_skip_to_closing_square_bracket (cp_parser *parser) +cp_parser_skip_up_to_closing_square_bracket (cp_parser *parser) { unsigned square_depth = 0; @@ -25427,21 +25481,30 @@ cp_parser_skip_to_closing_square_bracket (cp_parser *parser) case CPP_CLOSE_SQUARE: if (!square_depth--) - { - cp_lexer_consume_token (parser->lexer); - return true; - } + return true; break; default: break; } - /* Consume the token. */ + /* Consume the current token, skipping it. */ cp_lexer_consume_token (parser->lexer); } } +/* Consume tokens up to, and including, the next non-nested closing `]'. + Returns true iff we found a closing `]'. */ + +static bool +cp_parser_skip_to_closing_square_bracket (cp_parser *parser) +{ + bool found = cp_parser_skip_up_to_closing_square_bracket (parser); + if (found) + cp_lexer_consume_token (parser->lexer); + return found; +} + /* Return true if we are looking at an array-designator, false otherwise. */ static bool @@ -26284,6 +26347,56 @@ cp_parser_class_specifier (cp_parser* parser) cp_parser_late_parsing_nsdmi (parser, decl); } vec_safe_truncate (unparsed_nsdmis, 0); + + /* Now contract attributes. */ + FOR_EACH_VEC_SAFE_ELT (unparsed_contracts, ix, decl) + { + tree ctx = DECL_CONTEXT (decl); + if (class_type != ctx) + { + if (pushed_scope) + pop_scope (pushed_scope); + class_type = ctx; + pushed_scope = push_scope (class_type); + } + + temp_override cfd(current_function_decl, decl); + + /* Make sure that any template parameters are in scope. */ + maybe_begin_member_template_processing (decl); + + /* Make sure that any member-function parameters are in scope. + This function doesn't expect ccp to be set. */ + current_class_ptr = current_class_ref = NULL_TREE; + inject_parm_decls (decl); + + /* 'this' is not allowed in static member functions. */ + unsigned char local_variables_forbidden_p + = parser->local_variables_forbidden_p; + if (DECL_THIS_STATIC (decl)) + parser->local_variables_forbidden_p |= THIS_FORBIDDEN; + + /* Now we can parse contract conditions. */ + for (tree a = DECL_ATTRIBUTES (decl); a; a = TREE_CHAIN (a)) + { + if (cxx_contract_attribute_p (a)) + cp_parser_late_contract_condition (parser, decl, a); + } + + /* Restore the state of local_variables_forbidden_p. */ + parser->local_variables_forbidden_p = local_variables_forbidden_p; + + /* Remove any member-function parameters from the symbol table. */ + pop_injected_parms (); + + /* Remove any template parameters from the symbol table. */ + maybe_end_member_template_processing (); + + /* Perform any deferred contract matching. */ + match_deferred_contracts (decl); + } + vec_safe_truncate (unparsed_contracts, 0); + current_class_ptr = save_ccp; current_class_ref = save_ccr; if (pushed_scope) @@ -26387,6 +26500,8 @@ cp_parser_class_head (cp_parser* parser, /* Parse the attributes. */ attributes = cp_parser_attributes_opt (parser); + if (find_contract (attributes)) + diagnose_misapplied_contracts (attributes); /* If the next token is `::', that is invalid -- but sometimes people do try to write: @@ -29317,10 +29432,361 @@ cp_parser_std_attribute_list (cp_parser *parser, tree attr_ns) return attributes; } +/* Optionally parse a C++20 contract role. A NULL return means that no + contract role was specified. + + contract-role: + % default + % identifier + + If the identifier does not name a known contract role, it will + be assumed to be default. Returns the identifier for the role + token. */ + +static tree +cp_parser_contract_role (cp_parser *parser) +{ + gcc_assert (cp_lexer_next_token_is (parser->lexer, CPP_MOD)); + cp_lexer_consume_token (parser->lexer); + + cp_token *token = cp_lexer_peek_token (parser->lexer); + tree role_id = NULL_TREE; + if (token->type == CPP_NAME) + role_id = token->u.value; + else if (token->type == CPP_KEYWORD && token->keyword == RID_DEFAULT) + role_id = get_identifier ("default"); + else + { + error_at (token->location, "expected contract-role"); + return error_mark_node; + } + cp_lexer_consume_token (parser->lexer); + + /* FIXME: Warn about invalid/unknown roles? */ + return role_id; +} + +/* Parse an optional contract mode. + + contract-mode: + contract-semantic + [contract-level] [contract-role] + + contract-semantic: + check_never_continue + check_maybe_continue + check_always_continue + + contract-level: + default + audit + axiom + + contract-role: + default + identifier + + This grammar is taken from P1332R0. During parsing, this sets options + on the MODE object to determine the configuration of the contract. + + Returns a tree containing the identifiers used in the configuration. + This is either an IDENTIFIER with the literal semantic or a TREE_LIST + whose TREE_VALUE is the contract-level and whose TREE_PURPOSE is the + contract-role, if any. NULL_TREE is returned if no information is + given (i.e., all defaults selected). */ + +static tree +cp_parser_contract_mode_opt (cp_parser *parser, + bool postcondition_p) +{ + /* The mode is empty; the level and role are default. */ + if (cp_lexer_next_token_is (parser->lexer, CPP_COLON)) + return NULL_TREE; + + /* There is only a role; the level is default. */ + if (cp_lexer_next_token_is (parser->lexer, CPP_MOD)) + { + tree role_id = cp_parser_contract_role (parser); + return build_tree_list (role_id, get_identifier ("default")); + } + + /* Otherwise, match semantic or level. */ + cp_token *token = cp_lexer_peek_token (parser->lexer); + contract_level level = CONTRACT_INVALID; + contract_semantic semantic = CCS_INVALID; + tree config_id; + if (token->type == CPP_NAME) + { + config_id = token->u.value; + + /* Either a named level, a concrete semantic, or an identifier + for a postcondition. */ + const char *ident = IDENTIFIER_POINTER (token->u.value); + level = map_contract_level (ident); + semantic = map_contract_semantic (ident); + + /* The identifier is the return value for a postcondition. */ + if (level == CONTRACT_INVALID && semantic == CCS_INVALID + && postcondition_p) + return NULL_TREE; + } + else if (token->type == CPP_KEYWORD && token->keyword == RID_DEFAULT) + { + config_id = get_identifier ("default"); + level = CONTRACT_DEFAULT; + } + else + { + /* We got some other token other than a ':'. */ + error_at (token->location, "expected contract semantic or level"); + return NULL_TREE; + } + + /* Consume the literal semantic or level token. */ + cp_lexer_consume_token (parser->lexer); + + if (semantic == CCS_INVALID && level == CONTRACT_INVALID) + { + error_at (token->location, + "expected contract level: " + "%, %, or %"); + return NULL_TREE; + } + + /* We matched an explicit semantic. */ + if (semantic != CCS_INVALID) + { + if (cp_lexer_next_token_is (parser->lexer, CPP_MOD)) + { + error ("invalid use of contract role for explicit semantic"); + cp_lexer_consume_token (parser->lexer); + cp_lexer_consume_token (parser->lexer); + } + return config_id; + } + + /* We matched a level, there may be a role; otherwise this is default. */ + if (cp_lexer_next_token_is (parser->lexer, CPP_MOD)) + { + tree role_id = cp_parser_contract_role (parser); + return build_tree_list (role_id, config_id); + } + + return build_tree_list (NULL_TREE, config_id); +} + +static tree +find_error (tree *tp, int *, void *) +{ + if (*tp == error_mark_node) + return *tp; + return NULL_TREE; +} + +static bool +contains_error_p (tree t) +{ + return walk_tree (&t, find_error, NULL, NULL); +} + +/* Parse a standard C++20 contract attribute specifier. + + contract-attribute-specifier: + [ [ assert contract-level [opt] : conditional-expression ] ] + [ [ pre contract-level [opt] : conditional-expression ] ] + [ [ post contract-level [opt] identifier [opt] : conditional-expression ] ] + + For free functions, we cannot determine the type of the postcondition + identifier because the we haven't called grokdeclarator yet. In those + cases we parse the postcondition as if the identifier was declared as + 'auto '. We then instantiate the postcondition once the + return type is known. + + For member functions, contracts are in the complete-class context, so the + parse is deferred. We also have the return type avaialable (unless it's + deduced), so we don't need to parse the postcondition in terms of a + placeholder. */ + +static tree +cp_parser_contract_attribute_spec (cp_parser *parser, tree attribute) +{ + gcc_assert (contract_attribute_p (attribute)); + cp_token *token = cp_lexer_consume_token (parser->lexer); + location_t loc = token->location; + + bool assertion_p = is_attribute_p ("assert", attribute); + bool postcondition_p = is_attribute_p ("post", attribute); + + /* Parse the optional mode. */ + tree mode = cp_parser_contract_mode_opt (parser, postcondition_p); + + /* Check for postcondition identifiers. */ + cp_expr identifier; + if (postcondition_p && cp_lexer_next_token_is (parser->lexer, CPP_NAME)) + identifier = cp_parser_identifier (parser); + if (identifier == error_mark_node) + return error_mark_node; + + cp_parser_require (parser, CPP_COLON, RT_COLON); + + /* Defer the parsing of pre/post contracts inside class definitions. */ + tree contract; + if (!assertion_p && + current_class_type && + TYPE_BEING_DEFINED (current_class_type)) + { + /* Skip until we reach an unenclose ']'. If we ran into an unnested ']' + that doesn't close the attribute, return an error and let the attribute + handling code emit an error for missing ']]'. */ + cp_token *first = cp_lexer_peek_token (parser->lexer); + cp_parser_skip_to_closing_parenthesis_1 (parser, + /*recovering=*/false, + CPP_CLOSE_SQUARE, + /*consume_paren=*/false); + if (cp_lexer_peek_token (parser->lexer)->type != CPP_CLOSE_SQUARE + || cp_lexer_peek_nth_token (parser->lexer, 2)->type != CPP_CLOSE_SQUARE) + return error_mark_node; + cp_token *last = cp_lexer_peek_token (parser->lexer); + + /* Build a deferred-parse node. */ + tree condition = make_node (DEFERRED_PARSE); + DEFPARSE_TOKENS (condition) = cp_token_cache_new (first, last); + DEFPARSE_INSTANTIATIONS (condition) = NULL; + + /* And its corresponding contract. */ + contract = grok_contract (attribute, mode, identifier, condition, loc); + } + else + { + /* Enable location wrappers when parsing contracts. */ + auto suppression = make_temp_override (suppress_location_wrappers, 0); + + /* Build a fake variable for the result identifier. */ + tree result = NULL_TREE; + if (identifier) + { + begin_scope (sk_block, NULL_TREE); + result = make_postcondition_variable (identifier); + ++processing_template_decl; + } + + /* Parse the condition, ensuring that parameters or the return variable + aren't flagged for use outside the body of a function. */ + ++processing_contract_condition; + cp_expr condition = cp_parser_conditional_expression (parser); + --processing_contract_condition; + + /* Try to recover from errors by scanning up to the end of the + attribute. Sometimes we get partially parsed expressions, so + we need to search the condition for errors. */ + if (contains_error_p (condition)) + cp_parser_skip_up_to_closing_square_bracket (parser); + + /* Build the contract. */ + contract = grok_contract (attribute, mode, result, condition, loc); + + /* Leave our temporary scope for the postcondition result. */ + if (result) + { + --processing_template_decl; + pop_bindings_and_leave_scope (); + } + } + + if (!flag_contracts) + { + error_at (loc, "contracts are only available with %<-fcontracts%>"); + return error_mark_node; + } + + return finish_contract_attribute (attribute, contract); +} + +/* Parse a contract condition for a deferred contract. */ + +void cp_parser_late_contract_condition (cp_parser *parser, + tree fn, + tree attribute) +{ + tree contract = TREE_VALUE (TREE_VALUE (attribute)); + + /* Make sure we've gotten something that hasn't been parsed yet or that + we're not parsing an invalid contract. */ + tree condition = CONTRACT_CONDITION (contract); + if (TREE_CODE (condition) != DEFERRED_PARSE) + return; + + tree identifier = NULL_TREE; + if (TREE_CODE (contract) == POSTCONDITION_STMT) + identifier = POSTCONDITION_IDENTIFIER (contract); + + /* Build a fake variable for the result identifier. */ + tree result = NULL_TREE; + if (identifier) + { + /* TODO: Can we guarantee that the identifier has a location? */ + location_t loc = cp_expr_location (contract); + tree type = TREE_TYPE (TREE_TYPE (fn)); + if (!check_postcondition_result (fn, type, loc)) + { + invalidate_contract (contract); + return; + } + + begin_scope (sk_block, NULL_TREE); + result = make_postcondition_variable (identifier, type); + ++processing_template_decl; + } + + /* 'this' is not allowed in preconditions of constructors or in postconditions + of destructors. Note that the previous value of this variable is + established by the calling function, so we need to save it here. */ + tree saved_ccr = current_class_ref; + tree saved_ccp = current_class_ptr; + if ((DECL_CONSTRUCTOR_P (fn) && PRECONDITION_P (contract)) || + (DECL_DESTRUCTOR_P (fn) && POSTCONDITION_P (contract))) + { + current_class_ref = current_class_ptr = NULL_TREE; + parser->local_variables_forbidden_p |= THIS_FORBIDDEN; + } + + push_unparsed_function_queues (parser); + + /* Push the saved tokens onto the parser's lexer stack. */ + cp_token_cache *tokens = DEFPARSE_TOKENS (condition); + cp_parser_push_lexer_for_tokens (parser, tokens); + + /* Parse the condition, ensuring that parameters or the return variable + aren't flagged for use outside the body of a function. */ + ++processing_contract_condition; + condition = cp_parser_conditional_expression (parser); + --processing_contract_condition; + + /* Revert to the main lexer. */ + cp_parser_pop_lexer (parser); + + /* Restore the queue. */ + pop_unparsed_function_queues (parser); + + current_class_ref = saved_ccr; + current_class_ptr = saved_ccp; + + /* Commit to changes. */ + update_late_contract (contract, result, condition); + + /* Leave our temporary scope for the postcondition result. */ + if (result) + { + --processing_template_decl; + pop_bindings_and_leave_scope (); + } +} + /* Parse a standard C++-11 attribute specifier. attribute-specifier: [ [ attribute-using-prefix [opt] attribute-list ] ] + contract-attribute-specifier alignment-specifier attribute-using-prefix: @@ -29328,7 +29794,15 @@ cp_parser_std_attribute_list (cp_parser *parser, tree attr_ns) alignment-specifier: alignas ( type-id ... [opt] ) - alignas ( alignment-expression ... [opt] ). */ + alignas ( alignment-expression ... [opt] ). + + Extensions for contracts: + + contract-attribute-specifier: + [ [ assert : contract-mode [opt] : conditional-expression ] ] + [ [ pre : contract-mode [opt] : conditional-expression ] ] + [ [ post : contract-mode [opt] identifier [opt] : + conditional-expression ] ] */ static tree cp_parser_std_attribute_spec (cp_parser *parser) @@ -29340,10 +29814,27 @@ cp_parser_std_attribute_spec (cp_parser *parser) && cp_lexer_peek_nth_token (parser->lexer, 2)->type == CPP_OPEN_SQUARE) { tree attr_ns = NULL_TREE; + tree attr_name = NULL_TREE; cp_lexer_consume_token (parser->lexer); cp_lexer_consume_token (parser->lexer); + token = cp_lexer_peek_token (parser->lexer); + if (token->type == CPP_NAME) + { + attr_name = token->u.value; + attr_name = canonicalize_attr_name (attr_name); + } + + /* Handle contract-attribute-specs specially. */ + if (attr_name && contract_attribute_p (attr_name)) + { + tree attrs = cp_parser_contract_attribute_spec (parser, attr_name); + if (attrs != error_mark_node) + attributes = attrs; + goto finish_attrs; + } + if (cp_lexer_next_token_is_keyword (parser->lexer, RID_USING)) { token = cp_lexer_peek_nth_token (parser->lexer, 2); @@ -29372,6 +29863,7 @@ cp_parser_std_attribute_spec (cp_parser *parser) attributes = cp_parser_std_attribute_list (parser, attr_ns); + finish_attrs: if (!cp_parser_require (parser, CPP_CLOSE_SQUARE, RT_CLOSE_SQUARE) || !cp_parser_require (parser, CPP_CLOSE_SQUARE, RT_CLOSE_SQUARE)) cp_parser_skip_to_end_of_statement (parser); @@ -32396,6 +32888,14 @@ cp_parser_save_default_args (cp_parser* parser, tree decl) tree spec = TYPE_RAISES_EXCEPTIONS (TREE_TYPE (decl)); if (UNPARSED_NOEXCEPT_SPEC_P (spec)) vec_safe_push (unparsed_noexcepts, decl); + + /* Contracts are deferred. */ + for (tree attr = DECL_ATTRIBUTES (decl); attr; attr = TREE_CHAIN (attr)) + if (cxx_contract_attribute_p (attr)) + { + vec_safe_push (unparsed_contracts, decl); + break; + } } /* DEFAULT_ARG contains the saved tokens for the initializer of DECL, diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc index c3fc56a13ff..ecd6080d113 100644 --- a/gcc/cp/pt.cc +++ b/gcc/cp/pt.cc @@ -217,7 +217,6 @@ static tree get_underlying_template (tree); static tree tsubst_attributes (tree, tree, tsubst_flags_t, tree); static tree canonicalize_expr_argument (tree, tsubst_flags_t); static tree make_argument_pack (tree); -static void register_parameter_specializations (tree, tree); static tree enclosing_instantiation_of (tree tctx); static void instantiate_body (tree pattern, tree args, tree d, bool nested); static tree maybe_dependent_member_ref (tree, tree, tsubst_flags_t, tree); @@ -1968,6 +1967,16 @@ register_local_specialization (tree spec, tree tmpl) local_specializations->put (tmpl, spec); } +/* Registers T as a specialization of itself. This is used to preserve + the references to already-parsed parameters when instantiating + postconditions. */ + +void +register_local_identity (tree t) +{ + local_specializations->put (t, t); +} + /* TYPE is a class type. Returns true if TYPE is an explicitly specialized class. */ @@ -3161,8 +3170,10 @@ check_explicit_specialization (tree declarator, parm = DECL_CHAIN (parm)) DECL_CONTEXT (parm) = result; } - return register_specialization (tmpl, gen_tmpl, targs, + decl = register_specialization (tmpl, gen_tmpl, targs, is_friend, 0); + remove_contract_attributes (result); + return decl; } /* Set up the DECL_TEMPLATE_INFO for DECL. */ @@ -3262,6 +3273,10 @@ check_explicit_specialization (tree declarator, is_friend, 0); } + /* If this is a specialization, splice any contracts that may have + been inherited from the template, removing them. */ + if (decl != error_mark_node && DECL_TEMPLATE_SPECIALIZATION (decl)) + remove_contract_attributes (decl); /* A 'structor should already have clones. */ gcc_assert (decl == error_mark_node @@ -11576,6 +11591,113 @@ can_complete_type_without_circularity (tree type) static tree tsubst_omp_clauses (tree, enum c_omp_region_type, tree, tsubst_flags_t, tree); +/* Instantiate the contract statement. */ + +static tree +tsubst_contract (tree decl, tree t, tree args, tsubst_flags_t complain, + tree in_decl) +{ + tree type = decl ? TREE_TYPE (TREE_TYPE (decl)) : NULL_TREE; + bool auto_p = type_uses_auto (type); + + tree r = copy_node (t); + + /* Rebuild the result variable. */ + if (POSTCONDITION_P (t) && POSTCONDITION_IDENTIFIER (t)) + { + tree oldvar = POSTCONDITION_IDENTIFIER (t); + + tree newvar = copy_node (oldvar); + TREE_TYPE (newvar) = type; + DECL_CONTEXT (newvar) = decl; + POSTCONDITION_IDENTIFIER (r) = newvar; + + /* Make sure the postcondition is valid. */ + location_t loc = DECL_SOURCE_LOCATION (oldvar); + if (!auto_p) + if (!check_postcondition_result (decl, type, loc)) + return invalidate_contract (r); + + /* Make the variable available for lookup. */ + register_local_specialization (newvar, oldvar); + } + + /* Instantiate the condition. If the return type is undeduced, process + the expression as if inside a template to avoid spurious type errors. */ + if (auto_p) + ++processing_template_decl; + ++processing_contract_condition; + CONTRACT_CONDITION (r) + = tsubst_expr (CONTRACT_CONDITION (t), args, complain, in_decl, false); + --processing_contract_condition; + if (auto_p) + --processing_template_decl; + + /* And the comment. */ + CONTRACT_COMMENT (r) + = tsubst_expr (CONTRACT_COMMENT (r), args, complain, in_decl, false); + + return r; +} + +/* Update T by instantiating its contract attribute. */ + +static void +tsubst_contract_attribute (tree decl, tree t, tree args, + tsubst_flags_t complain, tree in_decl) +{ + /* For non-specializations, adjust the current declaration to the most general + version of in_decl. Because we defer the instantiation of contracts as long + as possible, they are still written in terms of the parameters (and return + type) of the most general template. */ + tree tmpl = DECL_TI_TEMPLATE (in_decl); + if (!DECL_TEMPLATE_SPECIALIZATION (tmpl)) + in_decl = DECL_TEMPLATE_RESULT (most_general_template (in_decl)); + local_specialization_stack specs (lss_copy); + register_parameter_specializations (in_decl, decl); + + /* Get the contract to be instantiated. */ + tree contract = CONTRACT_STATEMENT (t); + + /* Use the complete set of template arguments for instantiation. The + contract may not have been instantiated and still refer to outer levels + of template parameters. */ + args = DECL_TI_ARGS (decl); + + /* For member functions, make this available for semantic analysis. */ + tree save_ccp = current_class_ptr; + tree save_ccr = current_class_ref; + if (DECL_NONSTATIC_MEMBER_FUNCTION_P (decl)) + { + tree arg_types = TYPE_ARG_TYPES (TREE_TYPE (decl)); + tree this_type = TREE_TYPE (TREE_VALUE (arg_types)); + inject_this_parameter (this_type, cp_type_quals (this_type)); + } + + contract = tsubst_contract (decl, contract, args, complain, in_decl); + + current_class_ptr = save_ccp; + current_class_ref = save_ccr; + + /* Rebuild the attribute. */ + TREE_VALUE (t) = build_tree_list (NULL_TREE, contract); +} + +/* Rebuild the attribute list for DECL, substituting into contracts + as needed. */ + +void +tsubst_contract_attributes (tree decl, tree args, tsubst_flags_t complain, tree in_decl) +{ + tree list = copy_list (DECL_ATTRIBUTES (decl)); + for (tree attr = list; attr; attr = CONTRACT_CHAIN (attr)) + { + if (cxx_contract_attribute_p (attr)) + tsubst_contract_attribute (decl, attr, args, complain, in_decl); + } + DECL_ATTRIBUTES (decl) = list; +} + /* Instantiate a single dependent attribute T (a TREE_LIST), and return either T or a new TREE_LIST, possibly a chain in the case of a pack expansion. */ @@ -11585,6 +11707,10 @@ tsubst_attribute (tree t, tree *decl_p, tree args, { gcc_assert (ATTR_IS_DEPENDENT (t)); + /* Note that contract attributes are never substituted from this function. + Their instantiation is triggered by regenerate_from_template_decl when + we instantiate the body of the function. */ + tree val = TREE_VALUE (t); if (val == NULL_TREE) /* Nothing to do. */; @@ -17131,7 +17257,9 @@ tsubst_copy (tree t, tree args, tsubst_flags_t complain, tree in_decl) = do_auto_deduction (TREE_TYPE (r), init, auto_node, complain, adc_variable_type); } - gcc_assert (cp_unevaluated_operand || TREE_STATIC (r) + gcc_assert (cp_unevaluated_operand + || processing_contract_condition + || TREE_STATIC (r) || decl_constant_var_p (r) || seen_error ()); if (!processing_template_decl @@ -18649,6 +18777,19 @@ tsubst_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl, finish_using_directive (USING_STMT_NAMESPACE (t), /*attribs=*/NULL_TREE); break; + case PRECONDITION_STMT: + case POSTCONDITION_STMT: + gcc_unreachable (); + + case ASSERTION_STMT: + { + r = tsubst_contract (NULL_TREE, t, args, complain, in_decl); + if (r != error_mark_node) + add_stmt (r); + RETURN (r); + } + break; + case DECL_EXPR: { tree decl, pattern_decl; @@ -26180,6 +26321,21 @@ regenerate_decl_from_template (tree decl, tree tmpl, tree args) DECL_CONTEXT (t) = decl; } + if (DECL_CONTRACTS (decl)) + { + /* If we're regenerating a specialization, the contracts will have + been copied from the most general template. Replace those with + the ones from the actual specialization. */ + tree tmpl = DECL_TI_TEMPLATE (decl); + if (DECL_TEMPLATE_SPECIALIZATION (tmpl)) + { + remove_contract_attributes (decl); + copy_contract_attributes (decl, code_pattern); + } + + tsubst_contract_attributes (decl, args, tf_warning_or_error, code_pattern); + } + /* Merge additional specifiers from the CODE_PATTERN. */ if (DECL_DECLARED_INLINE_P (code_pattern) && !DECL_DECLARED_INLINE_P (decl)) @@ -26427,7 +26583,7 @@ maybe_instantiate_noexcept (tree fn, tsubst_flags_t complain) /* We're starting to process the function INST, an instantiation of PATTERN; add their parameters to local_specializations. */ -static void +void register_parameter_specializations (tree pattern, tree inst) { tree tmpl_parm = DECL_ARGUMENTS (pattern); diff --git a/gcc/cp/search.cc b/gcc/cp/search.cc index 10863a40b11..0dbb3be1ee7 100644 --- a/gcc/cp/search.cc +++ b/gcc/cp/search.cc @@ -30,6 +30,7 @@ along with GCC; see the file COPYING3. If not see #include "spellcheck-tree.h" #include "stringpool.h" #include "attribs.h" +#include "tree-inline.h" static int is_subobject_of_p (tree, tree); static tree dfs_lookup_base (tree, void *); @@ -2082,6 +2083,33 @@ check_final_overrider (tree overrider, tree basefn) } return 0; } + + if (!DECL_HAS_CONTRACTS_P (basefn) && DECL_HAS_CONTRACTS_P (overrider)) + { + auto_diagnostic_group d; + error ("function with contracts %q+D overriding contractless function", + overrider); + inform (DECL_SOURCE_LOCATION (basefn), + "overridden function is %qD", basefn); + return 0; + } + else if (DECL_HAS_CONTRACTS_P (basefn) && !DECL_HAS_CONTRACTS_P (overrider)) + { + /* We're inheriting basefn's contracts; create a copy of them but + replace references to their parms to our parms. */ + inherit_base_contracts (overrider, basefn); + } + else if (DECL_HAS_CONTRACTS_P (basefn) && DECL_HAS_CONTRACTS_P (overrider)) + { + /* We're in the process of completing the overrider's class, which means + our conditions definitely are not parsed so simply chain on the + basefn for later checking. + + Note that OVERRIDER's contracts will have been fully parsed at the + point the deferred match is run. */ + defer_guarded_contract_match (overrider, basefn, DECL_CONTRACTS (basefn)); + } + if (DECL_FINAL_P (basefn)) { auto_diagnostic_group d; diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc index 7c5f90b5127..c909a43fe52 100644 --- a/gcc/cp/semantics.cc +++ b/gcc/cp/semantics.cc @@ -610,7 +610,8 @@ set_cleanup_locs (tree stmts, location_t loc) if (TREE_CODE (stmts) == CLEANUP_STMT) { tree t = CLEANUP_EXPR (stmts); - protected_set_expr_location (t, loc); + if (t && TREE_CODE (t) != POSTCONDITION_STMT) + protected_set_expr_location (t, loc); /* Avoid locus differences for C++ cdtor calls depending on whether cdtor_returns_this: a conversion to void is added to discard the return value, and this conversion ends up carrying the location, and when it @@ -2169,7 +2170,8 @@ finish_non_static_data_member (tree decl, tree object, tree qualifying_scope, /* DR 613/850: Can use non-static data members without an associated object in sizeof/decltype/alignof. */ - if (is_dummy_object (object) && cp_unevaluated_operand == 0 + if (is_dummy_object (object) + && !cp_unevaluated_operand && (!processing_template_decl || !current_class_ref)) { if (complain & tf_error) @@ -2177,6 +2179,14 @@ finish_non_static_data_member (tree decl, tree object, tree qualifying_scope, if (current_function_decl && DECL_STATIC_FUNCTION_P (current_function_decl)) error ("invalid use of member %qD in static member function", decl); + else if (current_function_decl + && processing_contract_condition + && DECL_CONSTRUCTOR_P (current_function_decl)) + error ("invalid use of member %qD in constructor % contract", decl); + else if (current_function_decl + && processing_contract_condition + && DECL_DESTRUCTOR_P (current_function_decl)) + error ("invalid use of member %qD in destructor % contract", decl); else error ("invalid use of non-static data member %qD", decl); inform (DECL_SOURCE_LOCATION (decl), "declared here"); @@ -3010,6 +3020,10 @@ finish_this_expr (void) tree fn = current_nonlambda_function (); if (fn && DECL_STATIC_FUNCTION_P (fn)) error ("% is unavailable for static member functions"); + else if (fn && processing_contract_condition && DECL_CONSTRUCTOR_P (fn)) + error ("invalid use of % before it is valid"); + else if (fn && processing_contract_condition && DECL_DESTRUCTOR_P (fn)) + error ("invalid use of % after it is valid"); else if (fn) error ("invalid use of % in non-member function"); else @@ -3983,6 +3997,9 @@ process_outer_var_ref (tree decl, tsubst_flags_t complain, bool odr_use) } return error_mark_node; } + else if (processing_contract_condition && (TREE_CODE (decl) == PARM_DECL)) + /* Use of a parameter in a contract condition is fine. */ + return decl; else { if (complain & tf_error) @@ -4115,7 +4132,8 @@ finish_id_expression_1 (tree id_expression, body, except inside an unevaluated context (i.e. decltype). */ if (TREE_CODE (decl) == PARM_DECL && DECL_CONTEXT (decl) == NULL_TREE - && !cp_unevaluated_operand) + && !cp_unevaluated_operand + && !processing_contract_condition) { *error_msg = G_("use of parameter outside function body"); return error_mark_node; @@ -12286,6 +12304,10 @@ apply_deduced_return_type (tree fco, tree return_type) TREE_TYPE (fco) = change_return_type (return_type, TREE_TYPE (fco)); + maybe_update_postconditions (fco); + + /* Apply the type to the result object. */ + result = DECL_RESULT (fco); if (result == NULL_TREE) return; diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc index 45348c58bb6..074ba3ab1ff 100644 --- a/gcc/cp/tree.cc +++ b/gcc/cp/tree.cc @@ -46,6 +46,7 @@ static tree verify_stmt_tree_r (tree *, int *, void *); static tree handle_init_priority_attribute (tree *, tree, tree, int, bool *); static tree handle_abi_tag_attribute (tree *, tree, tree, int, bool *); +static tree handle_contract_attribute (tree *, tree, tree, int, bool *); /* If REF is an lvalue, returns the kind of lvalue that REF is. Otherwise, returns clk_none. */ @@ -3885,6 +3886,50 @@ called_fns_equal (tree t1, tree t2) return cp_tree_equal (t1, t2); } +bool comparing_override_contracts; + +/* In a component reference, return the innermost object of + the postfix-expression. */ + +static tree +get_innermost_component (tree t) +{ + gcc_assert (TREE_CODE (t) == COMPONENT_REF); + while (TREE_CODE (t) == COMPONENT_REF) + t = TREE_OPERAND (t, 0); + return t; +} + +/* Returns true if T is a possibly converted 'this' or '*this' expression. */ + +static bool +is_this_expression (tree t) +{ + t = get_innermost_component (t); + /* See through deferences and no-op conversions. */ + if (TREE_CODE (t) == INDIRECT_REF) + t = TREE_OPERAND (t, 0); + if (TREE_CODE (t) == NOP_EXPR) + t = TREE_OPERAND (t, 0); + return is_this_parameter (t); +} + +static bool +comparing_this_references (tree t1, tree t2) +{ + return is_this_expression (t1) && is_this_expression (t2); +} + +static bool +equivalent_member_references (tree t1, tree t2) +{ + if (!comparing_this_references (t1, t2)) + return false; + t1 = TREE_OPERAND (t1, 1); + t2 = TREE_OPERAND (t2, 1); + return t1 == t2; +} + /* Return truthvalue of whether T1 is the same tree structure as T2. Return 1 if they are the same. Return 0 if they are different. */ @@ -4219,6 +4264,13 @@ cp_tree_equal (tree t1, tree t2) return false; return true; + case COMPONENT_REF: + /* If we're comparing contract conditions of overrides, member references + compare equal if they designate the same member. */ + if (comparing_override_contracts) + return equivalent_member_references (t1, t2); + break; + default: break; } @@ -5034,6 +5086,8 @@ const struct attribute_spec std_attribute_table[] = handle_likeliness_attribute, attr_cold_hot_exclusions }, { "noreturn", 0, 0, true, false, false, false, handle_noreturn_attribute, attr_noreturn_exclusions }, + { "pre", 0, -1, false, false, false, false, handle_contract_attribute, NULL }, + { "post", 0, -1, false, false, false, false, handle_contract_attribute, NULL }, { NULL, 0, 0, false, false, false, false, NULL, NULL } }; @@ -5282,6 +5336,17 @@ handle_abi_tag_attribute (tree* node, tree name, tree args, return NULL_TREE; } +/* Perform checking for contract attributes. */ + +tree +handle_contract_attribute (tree *ARG_UNUSED (node), tree ARG_UNUSED (name), + tree ARG_UNUSED (args), int ARG_UNUSED (flags), + bool *ARG_UNUSED (no_add_attrs)) +{ + /* TODO: Is there any checking we could do here? */ + return NULL_TREE; +} + /* Return a new PTRMEM_CST of the indicated TYPE. The MEMBER is the thing pointed to by the constant. */ diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc index 2e0fd8fbf17..50185a0fac8 100644 --- a/gcc/cp/typeck.cc +++ b/gcc/cp/typeck.cc @@ -11260,11 +11260,22 @@ check_return_expr (tree retval, bool *no_warning) /* Actually copy the value returned into the appropriate location. */ if (retval && retval != result) - retval = cp_build_init_expr (result, retval); + { + /* If there's a postcondition for a scalar return value, wrap + retval in a call to the postcondition function. */ + if (tree post = apply_postcondition_to_return (retval)) + retval = post; + retval = cp_build_init_expr (result, retval); + } if (tree set = maybe_set_retval_sentinel ()) retval = build2 (COMPOUND_EXPR, void_type_node, retval, set); + /* If there's a postcondition for an aggregate return value, call the + postcondition function after the return object is initialized. */ + if (tree post = apply_postcondition_to_return (result)) + retval = build2 (COMPOUND_EXPR, void_type_node, retval, post); + return retval; } diff --git a/gcc/testsuite/g++.dg/contracts/backtrace_handler/assert_fail.cpp b/gcc/testsuite/g++.dg/contracts/backtrace_handler/assert_fail.cpp new file mode 100644 index 00000000000..08c64bc2849 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/backtrace_handler/assert_fail.cpp @@ -0,0 +1,23 @@ +void fun1() { + int x = 0; + [[ assert: x < 0 ]]; +} +namespace tns { + void fun2() { + fun1(); + } +} +template +void fun3(T a) { + tns::fun2(); +} +void fun4() { + fun3(5); +} +int main(int, char**) { + void (*fp)() = nullptr; + fp = fun4; + fp(); + return 0; +} + diff --git a/gcc/testsuite/g++.dg/contracts/backtrace_handler/handle_contract_violation.cpp b/gcc/testsuite/g++.dg/contracts/backtrace_handler/handle_contract_violation.cpp new file mode 100644 index 00000000000..bfbb97ec02e --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/backtrace_handler/handle_contract_violation.cpp @@ -0,0 +1,26 @@ +#include +#include +#include +#include + +static constexpr int MAX_BACKTRACE_DEPTH = 128; + +void handle_contract_violation(const std::contract_violation &violation) { + size_t _backtraceSize{0}; + void *_backtrace[MAX_BACKTRACE_DEPTH]{}; + + _backtraceSize = backtrace(_backtrace, MAX_BACKTRACE_DEPTH); + if(_backtraceSize == MAX_BACKTRACE_DEPTH) + std::cerr << "warning: backtrace may have been truncated" << std::endl; + + std::cerr << "contract violation: " << violation.file_name() + << ":" << violation.line_number() + << ": " << violation.comment() << " is false" + << " [with contract level=" << violation.assertion_level() << "]" << std::endl + << "violation occurs here:" << std::endl; + // skip the stack frame of handle_contract_violation and + // on_contract_violation to get to wherever the assert was + backtrace_symbols_fd(_backtrace + 2, _backtraceSize - 1, STDERR_FILENO); + std::cerr << "end of violation" << std::endl; +} + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-access1.C b/gcc/testsuite/g++.dg/contracts/contracts-access1.C new file mode 100644 index 00000000000..a3a29821017 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-access1.C @@ -0,0 +1,128 @@ +// ensure that that preconditions can access public, protected, and private +// members of the current and base classes +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts -fcontract-continuation-mode=on" } + +struct Base +{ + int pub{-1}; + + virtual int b() + [[ pre: pub > 0 ]] + [[ pre: pro > 0 ]] + [[ pre: pri > 0 ]] + { + return pub * pro * pri; + } + + protected: + int pro{-1}; + int pri{-1}; +}; + +struct Child : Base +{ + int fun() + [[ pre: pub > 0 ]] + [[ pre: pro > 0 ]] + [[ pre: pri > 0 ]] + { + return pub * pro; + } +}; + +struct VChild : Base +{ + int b() + [[ pre: pub > 0 ]] + [[ pre: pro > 0 ]] + [[ pre: pri > 0 ]] + { + return pub * pro; + } +}; + +template +struct TChild : B +{ + int fun() + [[ pre: B::pub > 0 ]] + [[ pre: B::pro > 0 ]] + [[ pre: B::pri > 0 ]] + { + return B::pub * B::pro; + } +}; + +struct PubBase +{ + int pub{-1}; + int pro{-1}; + int pri{-1}; +}; + +struct PubChild : PubBase +{ + int fun() + [[ pre: pub > 0 ]] + [[ pre: pro > 0 ]] + [[ pre: pri > 0 ]] + { + return pub * pro; + } +}; + +template +struct TPubChild : B +{ + int fun() + [[ pre: B::pub > 0 ]] + [[ pre: B::pro > 0 ]] + [[ pre: B::pri > 0 ]] + { + return B::pub * B::pro; + } +}; + +int main() +{ + Base base{}; + base.b(); + + Child child{}; + child.fun(); + + VChild vchild{}; + vchild.b(); + + TChild tchild{}; + tchild.fun(); + + PubChild pubchild{}; + pubchild.fun(); + + TPubChild tpubchild; + tpubchild.fun(); + + return 0; +} + +// { dg-output "default std::handle_contract_violation called: .*.C 11 Base::b .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 12 Base::b .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 13 Base::b .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 26 Child::fun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 27 Child::fun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 28 Child::fun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 37 VChild::b .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 38 VChild::b .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 39 VChild::b .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 49 TChild::fun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 50 TChild::fun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 51 TChild::fun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 67 PubChild::fun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 68 PubChild::fun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 69 PubChild::fun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 79 TPubChild::fun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 80 TPubChild::fun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 81 TPubChild::fun .*(\n|\r\n|\r)*" } + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-assume1.C b/gcc/testsuite/g++.dg/contracts/contracts-assume1.C new file mode 100644 index 00000000000..71388501ea7 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-assume1.C @@ -0,0 +1,30 @@ +// test that assumed contracts do instatiate templates +// { dg-do compile } +// { dg-options "-std=c++2a -fcontracts" } + +template +int f(T t) +{ + return -t; +} + +int dummy() +{ + [[ assert assume: f(1.0) > 0 ]]; + return -1; +} + +template<> +int f(double t) // { dg-error "specialization of.*after instantiation" } +{ + return -1.0; +} + +int main() +{ + dummy(); + f(1); + f(1.0); + return 0; +} + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-assume2.C b/gcc/testsuite/g++.dg/contracts/contracts-assume2.C new file mode 100644 index 00000000000..af163eddcf2 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-assume2.C @@ -0,0 +1,34 @@ +// ensure that assert contracts can be turned into compile time assumptions +// and that they can be used for optimization. +// +// Even though x == -1, the assert contract tells the compiler that it is +// safe to assume the x <= 0 branch is never taken fun can be transformed into +// just +// printf("%d: test x>0\n", x); +// return 0; +// we ensure this by matching on the output and expecting a 0 return code from +// main -- unlike contracts-ignore2 which expects a failing return code +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts -fcontract-role=default:never,assume,ignore -O1" } +#include + +int fun(int x) { + [[assert audit: x > 0]]; + if(x <= 0) + { + printf("%d: test x<=0 opt out\n", x); + return -1; + } + else + { + printf("%d: test x>0\n", x); + return 0; + } +} + +int main(int, char**) { + volatile int x = -1; + return fun(x); +} + +// { dg-output "-1: test x>0(\n|\r\n|\r)*" } diff --git a/gcc/testsuite/g++.dg/contracts/contracts-assume3.C b/gcc/testsuite/g++.dg/contracts/contracts-assume3.C new file mode 100644 index 00000000000..8dad6bb562e --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-assume3.C @@ -0,0 +1,19 @@ +// test that assumed contracts that reference undefined entities do not cause +// a link failure +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts" } + +int f(int t); + +int dummy() +{ + [[ assert assume: f(1) > 0 ]]; + return -1; +} + +int main() +{ + dummy(); + return 0; +} + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-assume4.C b/gcc/testsuite/g++.dg/contracts/contracts-assume4.C new file mode 100644 index 00000000000..7954f531612 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-assume4.C @@ -0,0 +1,19 @@ +// test that assumed constexpr contracts that reference undefined entities do +// not cause constexpr eval failure +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts" } + +constexpr int f(int t); // { dg-warning "used but never defined" } + +constexpr int dummy() +{ + [[ assert assume: f(1) > 0 ]]; + return -1; +} + +int main() +{ + constexpr int n = dummy(); + return 0; +} + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-assume5.C b/gcc/testsuite/g++.dg/contracts/contracts-assume5.C new file mode 100644 index 00000000000..372c0deb13a --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-assume5.C @@ -0,0 +1,34 @@ +// test that assumed constexpr contracts that reference defined entities, or +// undefined entities in unevaluated context, cause constexpr eval failure when +// the predicate is constexpr false +// { dg-do compile } +// { dg-options "-std=c++2a -fcontracts" } + +constexpr int f(int t) +{ + return -1; +} + +constexpr int dummy() +{ + [[ assert assume: f(1) > 0 ]]; + return -1; +} + +constexpr int undef(int t); + +constexpr int dummy2() +{ + [[ assert assume: sizeof(decltype(undef(1))) < 0 ]]; + return -1; +} + +int main() +{ + constexpr int n = dummy(); // { dg-message "in .constexpr. expansion" } + // { dg-error "contract predicate" "" { target *-*-* } 14 } + constexpr int m = dummy2(); // { dg-message "in .constexpr. expansion" } + // { dg-error "contract predicate" "" { target *-*-* } 22 } + return 0; +} + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-assume6.C b/gcc/testsuite/g++.dg/contracts/contracts-assume6.C new file mode 100644 index 00000000000..931c4d0c19c --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-assume6.C @@ -0,0 +1,61 @@ +// ensure that non-defined entities in assume contracts do not error +// { dg-do compile } +// { dg-options "-std=c++2a -fcontracts" } + +template +T id2(T n); + +int fun(int n) + [[ pre assume: id2(n) > 0 ]] + [[ pre: n > 0 ]] +{ + return -n; +} + +template +T tfun(T n) + [[ pre assume: id2(n) > 0 ]] + [[ pre: n > 0 ]] +{ + return -n; +} + +template +constexpr T id(T n); // { dg-warning "used but never defined" } + +template +constexpr T cfun(T n) + [[ pre assume: id(n) > 0 ]] + [[ pre: id(n) > 0 ]] // { dg-error "used before its definition" } +{ + return -n; +} + +template +constexpr T id3(T n) +{ + return n; +} + +template +constexpr T cfun2(T n) + [[ pre assume: id3(n) > 0 ]] // { dg-error "contract predicate" } +{ + return -n; +} + +template +constexpr T cfun3(T n) + [[ pre: id3(n) > 0 ]] // { dg-error "contract predicate" } +{ + return -n; +} + +int main() { + constexpr int n = cfun(-5); + constexpr int n2 = cfun2(-5); + constexpr int n3 = cfun3(-5); + fun(-5); + tfun(-5); +} + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-comdat1.C b/gcc/testsuite/g++.dg/contracts/contracts-comdat1.C new file mode 100644 index 00000000000..3384ae6225e --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-comdat1.C @@ -0,0 +1,19 @@ +// Contract condition functions should be local symbols in a comdat group with +// the guarded function. + +// { dg-do compile { target { c++20 && comdat_group } } } +// { dg-additional-options -fcontracts } +// { dg-final { scan-assembler-not "_Z1fi.pre,comdat" } } +// { dg-final { scan-assembler-not {(weak|globl)[^\n]*_Z1fi.pre} } } + +inline int f(int i) + [[ pre: i > 0 ]] +{ + return i; +} + +int main() +{ + if (f(42) != 42) + __builtin_abort (); +} diff --git a/gcc/testsuite/g++.dg/contracts/contracts-config1.C b/gcc/testsuite/g++.dg/contracts/contracts-config1.C new file mode 100644 index 00000000000..9e32bac535d --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-config1.C @@ -0,0 +1,36 @@ +// Small test to ensure that the level and role information printed by various +// contract configurations is correct. +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts -fcontract-role=default:maybe,maybe,ignore" } + +int fun(int n) + [[ post default r: r > 0 ]] +{ + return -n; +} + +int main(int, char **) +{ + [[ assert default: false ]]; + [[ assert: false ]]; + [[ assert audit: false ]]; + [[ assert default %new_role: false ]]; + [[ assert %new_role: false ]]; + [[ assert audit %new_role: false ]]; + [[ assert check_maybe_continue: false ]]; + [[ assert %default: false ]]; + [[ assert audit %default: false ]]; + fun(5); + return 0; +} + +// { dg-output "default std::handle_contract_violation called: .*main false default default 1.*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*main false default default 1.*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*main false audit default 1.*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*main false default new_role 1.*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*main false default new_role 1.*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*main false audit new_role 1.*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*main false 1.*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*main false default default 1.*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*main false audit default 1.*(\n|\r\n|\r)*" } + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-constexpr1.C b/gcc/testsuite/g++.dg/contracts/contracts-constexpr1.C new file mode 100644 index 00000000000..4c111358d9b --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-constexpr1.C @@ -0,0 +1,74 @@ +// ensure that passing pre/post do not affect constexpr functions +// ensure that failing pre/post generate an error at runtime in constexpr funcs +// { dg-do run } +// { dg-options "-std=c++20 -fcontracts -fcontract-continuation-mode=on" } + +constexpr int wfun(int a) + [[ pre: a > 0 ]] + [[ post r: r > 0 ]] +{ + return a; +} + +constexpr int ffun(int a) + [[ pre: a > 0 ]] + [[ post r: r > 0 ]] +{ + return a; +} + +template +constexpr int tfun(T a) + [[ pre: a > 0 ]] + [[ post r: r > 0 ]] +{ + return a; +} + +template +constexpr int wtfun(T a) + [[ pre: a > 0 ]] + [[ post r: r > 0 ]] +{ + return a; +} + +template +constexpr int ftfun(T a) + [[ pre: a > 0 ]] + [[ post r: r > 0 ]] +{ + return a; +} + +constexpr int explicitfn(int a) + [[ pre ignore: a > 0 ]] + [[ pre check_maybe_continue: a > 0 ]] + [[ post ignore r: r > 0 ]] + [[ post check_maybe_continue r: r > 0 ]] +{ + return a; +} + +int main(int, char **) { + constexpr int a = wfun(10); + int b = ffun(-10); + constexpr int c = wtfun(10); + int d = ftfun(-10); + + int e = explicitfn(-10); + + int z = ftfun(-10.0); + + return 0; +} + +// { dg-output "default std::handle_contract_violation called: .*.C 14 ffun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 15 ffun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 38 ftfun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 39 ftfun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 46 explicitfn .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 48 explicitfn .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 38 ftfun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 39 ftfun .*(\n|\r\n|\r)*" } + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-constexpr2.C b/gcc/testsuite/g++.dg/contracts/contracts-constexpr2.C new file mode 100644 index 00000000000..d0d41f05927 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-constexpr2.C @@ -0,0 +1,58 @@ +// ensure that failing pre/post can fail at constexpr time +// { dg-do compile } +// { dg-options "-std=c++20 -fcontracts -fcontract-continuation-mode=on" } + +constexpr int ffun(int a) + [[ pre: a > 0 ]] + [[ post r: r > 10 ]] +{ + return a; +} + +template +constexpr int ftfun(T a) + [[ pre: a > 0 ]] + [[ post r: r > 10 ]] +{ + return a; +} + +constexpr int explicitfn(int a) + [[ pre ignore: a > 0 ]] + [[ pre check_maybe_continue: a > 0 ]] + [[ post ignore r: r > 10 ]] + [[ post check_maybe_continue r: r > 10 ]] +{ + return a; +} + +template +constexpr int ftfun2(T a) + [[ pre: a > 0 ]] + [[ post r: r > 10 ]] +{ + return a; +} + +int main(int, char **) { + constexpr int a = ffun(-10); + // { dg-error "contract predicate" "" { target *-*-* } 6 } + constexpr int b = ftfun(-10); + // { dg-error "contract predicate" "" { target *-*-* } 14 } + constexpr int c = explicitfn(-10); + // { dg-error "contract predicate" "" { target *-*-* } 22 } + constexpr int d = ftfun2(-10.0); + // { dg-error "contract predicate" "" { target *-*-* } 31 } + + constexpr int e = ffun(5); + // { dg-error "contract predicate" "" { target *-*-* } 7 } + constexpr int f = ftfun(5); + // { dg-error "contract predicate" "" { target *-*-* } 15 } + constexpr int g = explicitfn(5); + // { dg-error "contract predicate" "" { target *-*-* } 24 } + constexpr int h = ftfun2(5.5); + // { dg-error "contract predicate" "" { target *-*-* } 32 } + + return 0; +} + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-constexpr3.C b/gcc/testsuite/g++.dg/contracts/contracts-constexpr3.C new file mode 100644 index 00000000000..8826220ef91 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-constexpr3.C @@ -0,0 +1,10 @@ +// An assumed contract shouldn't break constant evaluation. + +// { dg-do compile { target c++20 } } +// { dg-additional-options -fcontracts } + +bool b; + +constexpr int f() [[ pre assume: b ]] { return 42; } + +static_assert (f() > 0); diff --git a/gcc/testsuite/g++.dg/contracts/contracts-conversion1.C b/gcc/testsuite/g++.dg/contracts/contracts-conversion1.C new file mode 100644 index 00000000000..ff5e23f2f14 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-conversion1.C @@ -0,0 +1,19 @@ +// Test to ensure that diagnostic location for condition conversion is in the +// right place. +// { dg-do compile } +// { dg-options "-std=c++2a -fcontracts" } + + +template +void fn() + [[ pre: T{} ]] // { dg-error "no match" } +{ +} + +struct Z { }; + +int main(int, char**) { + fn(); + fn(); + return 0; +} diff --git a/gcc/testsuite/g++.dg/contracts/contracts-ctor-dtor1.C b/gcc/testsuite/g++.dg/contracts/contracts-ctor-dtor1.C new file mode 100644 index 00000000000..bcd6096b5a7 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-ctor-dtor1.C @@ -0,0 +1,177 @@ +// Tests to ensure that contracts are properly emitted for constructors, +// destructors, and their intersection with templates. +// { dg-do compile } +// { dg-options "-std=c++2a -fcontracts" } + +bool pre_{false}, post_{false}; +bool delegate_{false}; + +struct S +{ + S() [[ pre: pre_ ]] [[ post: post_ ]]; + ~S() [[ pre: pre_ ]] [[ post: post_ ]]; +}; + +S::S() { return; } +S::~S() { return; } + +struct SInline +{ + SInline() [[ pre: pre_ ]] [[ post: post_ ]] { return; } + ~SInline() [[ pre: pre_ ]] [[ post: post_ ]] { return; } +}; + +struct SDelegate0 +{ + SDelegate0(int) [[ pre: pre_ ]] [[ post: post_ ]] { return; } + SDelegate0() : SDelegate0(0) { return; } + ~SDelegate0() [[ pre: pre_ ]] [[ post: post_ ]] { return; } +}; + +struct SDelegate1 +{ + SDelegate1(int) { return; } + SDelegate1() [[ pre: pre_ ]] [[ post: post_ ]] : SDelegate1(0) { return; } + ~SDelegate1() [[ pre: pre_ ]] [[ post: post_ ]] { return; } +}; + +struct SDelegate2 +{ + SDelegate2(int) [[ pre: pre_ ]] [[ post: post_ ]] { return; } + SDelegate2() [[ pre: pre_ && delegate_ ]] [[ post: post_ && delegate_ ]] : SDelegate2(0) { return; } + ~SDelegate2() [[ pre: pre_ ]] [[ post: post_ ]] { return; } +}; + +struct SDelegate3 +{ + SDelegate3(int) [[ pre: pre_ ]] [[ post: post_ ]] { return; } + SDelegate3() [[ pre: pre_ && delegate_ ]] [[ post: post_ && delegate_ ]] : SDelegate3(0) { return; } + ~SDelegate3() [[ pre: pre_ ]] [[ post: post_ ]] { return; } +}; + +template +struct S1 +{ + S1() [[ pre: pre_ ]] [[ post: post_ ]] { } +}; + +struct S2 +{ + template + S2(T) [[ pre: pre_ ]] [[ post: post_ ]] { } +}; + +template +struct S3 +{ + template + S3(S) [[ pre: pre_ ]] [[ post: post_ ]] { } +}; + +struct G0 +{ + G0() [[ post: x > 0 ]] {} + ~G0() [[ pre: x > 0 ]] {} + int x{-1}; +}; + +struct G1 +{ + G1() [[ post: this->x > 0 ]] {} + ~G1() [[ pre: this->x > 0 ]] {} + int x{-1}; +}; + +int x{-1}; + +struct G2 +{ + G2() [[ pre: ::x > 0 ]] {} + ~G2() [[ post: ::x > 0 ]] {} + int x{1}; +}; + +void test0() +{ + S s; + SInline si; + SDelegate0 sd0; + SDelegate1 sd1; + SDelegate2 sd2; + SDelegate3 sd3; + S1 s1_i; + S1 s1_d; + S2 s2_i{1}; + S2 s2_d{.1}; + S3 s3_i_i{1}; + S3 s3_i_d{.1}; + S3 s3_d_i{1}; + S3 s3_d_d{.1}; +} + +void test1() +{ + G0 g0; + G1 g1; + G2 g2; +} + +int main(int, char**) +{ + test0(); + test1(); + return 0; +}; + +// test0 +// { dg-output "default std::handle_contract_violation called: .*.C 11 S::S .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 11 S::S .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 20 SInline::SInline .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 20 SInline::SInline .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 26 SDelegate0::SDelegate0 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 26 SDelegate0::SDelegate0 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 34 SDelegate1::SDelegate1 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 34 SDelegate1::SDelegate1 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 41 SDelegate2::SDelegate2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 40 SDelegate2::SDelegate2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 40 SDelegate2::SDelegate2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 41 SDelegate2::SDelegate2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 48 SDelegate3::SDelegate3 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 47 SDelegate3::SDelegate3 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 47 SDelegate3::SDelegate3 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 48 SDelegate3::SDelegate3 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 55 S1::S1 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 55 S1::S1 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 55 S1::S1 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 55 S1::S1 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 61 S2::S2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 61 S2::S2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 61 S2::S2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 61 S2::S2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 68 S3::S3 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 68 S3::S3 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 68 S3::S3 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 68 S3::S3 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 68 S3::S3 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 68 S3::S3 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 68 S3::S3 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 68 S3::S3 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 49 SDelegate3::~SDelegate3 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 49 SDelegate3::~SDelegate3 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 42 SDelegate2::~SDelegate2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 42 SDelegate2::~SDelegate2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 35 SDelegate1::~SDelegate1 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 35 SDelegate1::~SDelegate1 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 28 SDelegate0::~SDelegate0 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 28 SDelegate0::~SDelegate0 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 21 SInline::~SInline .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 21 SInline::~SInline .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 12 S::~S .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 12 S::~S .*(\n|\r\n|\r)*" } + +// test1 +// { dg-output "default std::handle_contract_violation called: .*.C 73 G0::G0 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 80 G1::G1 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 81 G1::~G1 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 74 G0::~G0 .*(\n|\r\n|\r)*" } + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-ctor-dtor2.C b/gcc/testsuite/g++.dg/contracts/contracts-ctor-dtor2.C new file mode 100644 index 00000000000..ba3b7678ef6 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-ctor-dtor2.C @@ -0,0 +1,35 @@ +// Tests to ensure that an invalid this parm cannot be used in pre on ctors or +// in post on dtors. +// { dg-do compile } +// { dg-options "-std=c++2a -fcontracts" } + +struct F0 +{ + F0() [[ pre: x > 0 ]]; // { dg-error "invalid use of member" } + ~F0() [[ post: x > 0 ]]; // { dg-error "invalid use of member" } + int x{-1}; +}; + +struct F1 +{ + F1() [[ pre: this->x > 0 ]]; // { dg-error "may not be used" } + ~F1() [[ post: this->x > 0 ]]; // { dg-error "may not be used" } + int x{-1}; +}; + +struct F2 +{ + F2() + [[ post ret: false ]] // { dg-error "does not return a value" } + { + } + ~F2() + [[ post r: false ]] // { dg-error "does not return a value" } + { + } + void f() + [[ post r: false ]] // { dg-error "does not return a value" } + { + } +}; + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-cv1.C b/gcc/testsuite/g++.dg/contracts/contracts-cv1.C new file mode 100644 index 00000000000..8266b4fed8f --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-cv1.C @@ -0,0 +1,37 @@ +// Tests to ensure that contracts have a properly cv qualified this +// { dg-do compile } +// { dg-options "-std=c++2a -fcontracts" } + +struct S +{ + int g() const { return x_; } + int f() { return x_; } + + void mem_c() const + [[ pre: f() ]] // { dg-error "discards qualifiers" } + { + } + void mem_nc() + [[ pre: f() ]] + { + } + + void memc_c() const + [[ pre: g() ]] + { + } + void memc_nc() + [[ pre: g() ]] + { + } + + private: + int x_{-10}; +}; + +int main(int, char**) +{ + S s; + return 0; +}; + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-deduced1.C b/gcc/testsuite/g++.dg/contracts/contracts-deduced1.C new file mode 100644 index 00000000000..200ec734800 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-deduced1.C @@ -0,0 +1,108 @@ +// Tests to ensure that deduced return types work with postconditions using +// the return value on defining declarations. +// { dg-do compile } +// { dg-options "-std=c++2a -fcontracts --param ggc-min-heapsize=0 --param ggc-min-expand=0" } + +auto undeduced(int z) +{ + if (!(sizeof(decltype(undeduced(5))) > 4)) // { dg-error "before deduction" } + return 5; + return 4; +} + +// defining declaration, fine +auto g0(int a) [[ pre: a < 0 ]] [[ post r: r > 0 ]] +{ + return -a * 1.2; +} + +// non defining post using nondeduced identifier, fine +int g1(int m) [[ post r: r == m ]]; + +int g1(int n) [[ post s: s == n ]] +{ + return -n; +} + +int g2(int z) + [[ pre: sizeof(decltype(g2(5))) > 4 ]]; // { dg-error "not declared" } + +int g3(int z) + [[ pre: sizeof(decltype(g2(5))) > 4 ]] +{ + return -z; +} + +// deduced that doesn't use return, good +auto g4(int m) [[ post: m ]]; + +auto g4(int m) [[ post: m ]] +{ + return -m; +} + +auto g5(int m) [[ pre: m ]]; + +auto g5(int m) [[ pre: m ]] +{ + return -m; +} + +template +auto g6(T t) [[ post r: r == t ]]; + +template +auto g6(S s) [[ post q: q == s ]] +{ + return s; +} + +template +T g7(T t) [[ post r: r == t ]]; + +template +S g7(S s) [[ post q: q == s ]] +{ + return s; +} + +template +auto g8(T t) [[ post r: r == t && sizeof(decltype(::g8(t))) > 2 ]]; // { dg-error "not been declared" } + +// This failure is related to the fact that we've invalidated the previous +// contract. +template +auto g8(S s) [[ post q: q == s && sizeof(decltype(::g8(s))) > 2 ]] // { dg-error "mismatched" } +{ + return s; +} + +// non defining pre, bad +auto f0(int z) + [[ pre: sizeof(decltype(f0(5))) > 4 ]]; // { dg-error "not declared" } + +// defining pre, still ill formed +auto f1(int z) + [[ pre: sizeof(decltype(f1(5))) > 4 ]] // { dg-error "not declared" } +{ + return '5'; +} + +// undeduced using postcon, OK +auto f2(int m) [[ post r: r == m ]]; + +auto f2(int n) [[ post s: s == n ]] +{ + return n; +} + +template +void f3(T t) [[ post r: false ]] // { dg-error "function does not return a value" } +{ +} + +int main(int, char**) +{ + return 0; +} + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-deduced2.C b/gcc/testsuite/g++.dg/contracts/contracts-deduced2.C new file mode 100644 index 00000000000..da9c019f10a --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-deduced2.C @@ -0,0 +1,84 @@ +// check that contracts work around deduced return types +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts -fcontract-continuation-mode=on" } + +auto g0(int a) [[ pre: a < 0 ]] [[ post r: r > 0 ]] +{ + return -a * 1.2; +} + +int g1(int m) [[ post r: r == m ]]; + +int g1(int n) [[ post s: s == n ]] +{ + return -n; +} + +int g2(int z) +{ + return -z; +} + +int g3(int z) + [[ pre: sizeof(decltype(g2(5))) > 4 ]] +{ + return -z; +} + +auto g4(int m) [[ post: m ]]; + +auto g4(int m) [[ post: m ]] +{ + return -m; +} + +auto g5(int m) [[ pre: m ]]; + +auto g5(int m) [[ pre: m ]] +{ + return -m; +} + +template +auto g6(T t) [[ post r: r == t ]]; + +template +auto g6(S s) [[ post q: q == s ]] +{ + return -s; +} + +// template +// T g7(T t) [[ post r: r == t ]]; + +template +S g7(S s) [[ post q: q == s ]] +{ + return -s; +} + +int main(int, char**) { + g0(5); + g1(6); + g2(1); + g3(1); + g4(0); + g5(0); + g6(5); + g6(5.5); + g7(5); + g7(6.6); + return 0; +} + +// { dg-output "default std::handle_contract_violation called: .*.C 5 g0 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 5 g0 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 12 g1 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 23 g3 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 30 g4 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 37 g5 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 46 g6 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 46 g6 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 55 g7 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 55 g7 .*(\n|\r\n|\r)*" } + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-friend1.C b/gcc/testsuite/g++.dg/contracts/contracts-friend1.C new file mode 100644 index 00000000000..0ccfbe2c7c3 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-friend1.C @@ -0,0 +1,40 @@ +// ensure contracts on friend declarations are a complete class context +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts -fcontract-continuation-mode=on" } + +struct X { + friend void fn0(X x) [[ pre: x.a > 0 ]] { } + + friend void fn2(X x); + static void fns0(X x) [[ pre: x.a > 0 ]] { } + static void fns1(X x) [[ pre: x.a > 0 ]]; + static void fns2(X x); + + friend void fn(X &x) { x.a = -5; } + +private: + int a{10}; +}; + +void fn2(X x) [[ pre: x.a > 0 ]] { } +void X::fns1(X x) { } +void X::fns2(X x) [[ pre: x.a > 0 ]] { } + +int main(int, char**) { + X x; + fn(x); // no contract + + fn0(x); + fn2(x); + + X::fns0(x); + X::fns1(x); + X::fns2(x); + return 0; +} + +// { dg-output "default std::handle_contract_violation called: .*.C 6 fn0 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 19 fn2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 9 X::fns0 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 10 X::fns1 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 21 X::fns2 .*(\n|\r\n|\r)*" } diff --git a/gcc/testsuite/g++.dg/contracts/contracts-ft1.C b/gcc/testsuite/g++.dg/contracts/contracts-ft1.C new file mode 100644 index 00000000000..e5d5be7cb6a --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-ft1.C @@ -0,0 +1,14 @@ +// { dg-do compile } + +#ifdef __cpp_contracts +static_assert (false); +#endif + +#ifdef __cpp_contracts_literal_semantics +static_assert (false); +#endif + +#ifdef __cpp_contracts_roles +static_assert (false); +#endif + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-ignore1.C b/gcc/testsuite/g++.dg/contracts/contracts-ignore1.C new file mode 100644 index 00000000000..c8ae6568166 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-ignore1.C @@ -0,0 +1,30 @@ +// test that ignored contracts do instatiate templates +// { dg-do compile } +// { dg-options "-std=c++2a -fcontracts" } + +template +int f(T t) +{ + return -t; +} + +int dummy() +{ + [[ assert ignore: f(1.0) > 0 ]]; + return -1; +} + +template<> +int f(double t) // { dg-error "specialization of.*after instantiation" } +{ + return -1.0; +} + +int main() +{ + dummy(); + f(1); + f(1.0); + return 0; +} + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-ignore2.C b/gcc/testsuite/g++.dg/contracts/contracts-ignore2.C new file mode 100644 index 00000000000..5cf800a3559 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-ignore2.C @@ -0,0 +1,26 @@ +// baseline for testing assert contracts being turned into compile time +// assumptions; see contracts-assume2 for the assumed case +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts" } +#include + +int fun(int x) { + [[assert audit: x > 0]]; + if(x <= 0) + { + printf("%d: test x<=0 opt out\n", x); + return -1; + } + else + { + printf("%d: test x>0\n", x); + return 0; + } +} + +int main(int, char**) { + volatile int x = -1; + return fun(x); +} + +// { dg-shouldfail "" } diff --git a/gcc/testsuite/g++.dg/contracts/contracts-large-return.C b/gcc/testsuite/g++.dg/contracts/contracts-large-return.C new file mode 100644 index 00000000000..ea8e73a387f --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-large-return.C @@ -0,0 +1,15 @@ +// { dg-do compile } +// { dg-options "-std=c++2a -fcontracts" } + +struct Foo +{ + int x; + bool y; + long z[4]; +}; + +Foo foo() [[ pre: true ]] +{ + return {}; +} + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-multiline1.C b/gcc/testsuite/g++.dg/contracts/contracts-multiline1.C new file mode 100644 index 00000000000..8145c61e827 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-multiline1.C @@ -0,0 +1,19 @@ +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts -fcontract-continuation-mode=on" } + +int main(int, char **) +{ + int x = 5; + int y = 10; + [[ assert: + x + < + 10 + && + y + > + 123 + ]]; +} + +// { dg-output "default std::handle_contract_violation called: .*.C 8 main x < 10 && y > 123.*(\n|\r\n|\r)*" } diff --git a/gcc/testsuite/g++.dg/contracts/contracts-multiple-inheritance1.C b/gcc/testsuite/g++.dg/contracts/contracts-multiple-inheritance1.C new file mode 100644 index 00000000000..3f2f5edd6ef --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-multiple-inheritance1.C @@ -0,0 +1,15 @@ +// { dg-do compile } +// { dg-options "-std=c++2a -fcontracts -fcontract-continuation-mode=on" } + +struct BaseA { + virtual int fun(int n) [[ pre: n > 0 ]] { return -n; } +}; + +struct BaseB { + virtual int fun(int n) [[ pre: n > 0 ]] { return -n; } +}; + +struct Child : public BaseA, BaseB { + int fun(int n) [[ pre: n > 0 ]] { return -n; } +}; + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-multiple-inheritance2.C b/gcc/testsuite/g++.dg/contracts/contracts-multiple-inheritance2.C new file mode 100644 index 00000000000..37bdac1d63f --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-multiple-inheritance2.C @@ -0,0 +1,33 @@ +// { dg-do compile } +// { dg-options "-std=c++2a -fcontracts -fcontract-continuation-mode=on" } + +struct BaseA { + virtual int fun(int n) [[ pre: n > 0 ]] { return -n; } +}; + +struct BaseB { + virtual int fun(int n) [[ pre: n < 0 ]] { return -n; } +}; + +struct Child1 : public BaseA, BaseB { + int fun(int n) [[ pre: n > 0 ]] { return -n; } // { dg-error "mismatched" } +}; + +struct Child2 : public BaseA, BaseB { + int fun(int n) [[ pre: n < 0 ]] { return -n; } // { dg-error "mismatched" } +}; + +struct Child3 : public BaseA, BaseB { + int fun(int n) { return -n; } +}; + +struct Child4 : public BaseA { + int fun(int n); +}; + +int Child4::fun(int n) + [[ pre: n != 0 ]] // { dg-error "mismatched" } +{ + return -n; +} + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-nested-class1.C b/gcc/testsuite/g++.dg/contracts/contracts-nested-class1.C new file mode 100644 index 00000000000..9f91ff499d0 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-nested-class1.C @@ -0,0 +1,24 @@ +// { dg-do compile } +// { dg-options "-std=c++2a -fcontracts" } + +void gfn3(int n) [[ pre: n > 0 ]]; + +struct Outer { + struct Inner { + void fn(int n) [[ pre: n > 0 && bob > 1 ]]; + void fn2(int n) [[ pre: n > 0 && bob > 1 ]]; + }; + + void fn(int m) [[ pre: m > 1 ]]; + friend void Inner::fn(int n) [[ pre: n > 0 && bob > 1 ]]; // { dg-error "not declared" } + + friend void gfn(int p) [[ pre: p > 0 ]]; + friend void gfn(int q) [[ pre: q > 1 ]]; // { dg-error "'q' was not declared" } + + // This should be okay. + friend void gfn2(int q); + friend void gfn2(int p) [[ pre: p > 0 ]] { } + + static int bob; +}; +int Outer::bob{-1}; diff --git a/gcc/testsuite/g++.dg/contracts/contracts-nested-class2.C b/gcc/testsuite/g++.dg/contracts/contracts-nested-class2.C new file mode 100644 index 00000000000..43e75edbef8 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-nested-class2.C @@ -0,0 +1,40 @@ +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts -fcontract-continuation-mode=on" } + +void gfn3(int n) [[ pre: n > 0 ]]; + +struct Outer { + struct Inner { + void fn(int n) [[ pre: n > 0 && bob > 1 ]]; + }; + + void fn(int m) [[ pre: m > 1 ]]; + + friend void gfn1(int q); + friend void gfn1(int p) [[ pre: p > 0 ]] { } + + friend void gfn2(int q, Outer *); + friend void gfn2(int p, Outer *) [[ pre: p > 0 ]] { } + + friend void gfn3(int n); + + static int bob; +}; +int Outer::bob{-1}; + +void Outer::Inner::fn(int x) { } +void Outer::fn(int y) { } + +void gfn3(int n) { } +void gfn1(int q); + +int main(int, char **) { + Outer::Inner in; + in.fn(-5); + Outer out; + out.fn(-6); + gfn1(-7); + gfn2(-8, &out); + gfn3(-9); +} + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-nocopy1.C b/gcc/testsuite/g++.dg/contracts/contracts-nocopy1.C new file mode 100644 index 00000000000..4608a4dc181 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-nocopy1.C @@ -0,0 +1,24 @@ +// Contracts shouldn't introduce more copies. + +// { dg-do compile { target c++20 } } +// { dg-additional-options -fcontracts } + +struct A +{ + int i; + A(int i): i(i) { } + A(const A&) = delete; +}; + +A f(A a) + [[ pre: a.i > 0 ]] + [[ post r: r.i > 0 ]] +{ + return {a.i}; +} + +int main() +{ + if (f({42}).i != 42) + __builtin_abort (); +} diff --git a/gcc/testsuite/g++.dg/contracts/contracts-override.C b/gcc/testsuite/g++.dg/contracts/contracts-override.C new file mode 100644 index 00000000000..f96aa988e0f --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-override.C @@ -0,0 +1,43 @@ +// { dg-do compile } +// { dg-options "-std=c++2a -fcontracts" } + +struct Foo { + virtual int f0(int n) [[ pre: false ]] { return n; } + virtual int f1(int n) [[ pre: false ]] { return n; } + virtual int f2(int n) [[ pre: false ]] { return n; } + virtual int f3(int n) [[ pre: false ]] { return n; } + virtual int f4(int n) [[ pre: false ]] { return n; } + virtual int f5(int n) [[ pre: false ]] { return n; } + virtual int f6(int n) [[ pre: false ]] { return n; } + virtual int f7(int n) [[ pre: false ]] { return n; } + virtual int f8(int n) [[ pre: false ]] { return n; } + virtual int f9(int n) [[ pre: false ]] { return n; } + virtual int f10(int n) [[ pre: false ]] { return n; } + virtual int f11(int n) [[ pre: n > 0 ]] [[ pre: n > 1 ]] { return n; } + virtual int f12(int n) [[ pre: n > 0 ]] [[ pre: n > 1 ]] { return n; } +}; + +struct Bar : Foo { + [[ pre: n > -1 ]] int f0(int n = 0) override { return -n; } // { dg-error "contracts must appertain" } + int [[ pre: n > -2 ]] f1(int n = 0) override { return -n; } // { dg-error "contracts must appertain" } + int f2 [[ pre: n > -3 ]] (int n = 0) override { return -n; } // { dg-error "contracts must appertain" } + int f4([[ pre: n > -4 ]] int n = 0) override { return -n; } // { dg-error "contracts must appertain" } + int f5(int [[ pre: n > -5 ]] n = 0) override { return -n; } // { dg-error "contracts must appertain" } + int f6(int n [[ pre: n > -6 ]] = 0) override { return -n; } // { dg-error "contracts must appertain" } + int f7(int n = [[ pre: n > -7 ]] 0) override { return -n; } + // { dg-error "expected identifier" "" { target *-*-* } .-1 } + // { dg-error "expected .\{. before numeric" "" { target *-*-* } .-2 } + // { dg-error "invalid user-defined conversion" "" { target *-*-* } .-3 } + // { dg-error "expected .,." "" { target *-*-* } .-4 } + int f8(int n = 0 [[ pre: n > -8 ]]) override { return -n; } + // { dg-error "shall only introduce an attribute" "" { target *-*-* } .-1 } + int f9(int n = 0) [[ pre: n > -9 ]] override { return -n; } // { dg-error "mismatched contract" } + + // The grammar doesn't appear to permit contracts after the virt-specifiers + // but the parser will happily add these to an attribute list that is not + // owned by the function declarator. + int f10(int n = 0) override [[ pre: n > -10 ]] { return -n; } // { dg-error "contracts must appertain" } + int f11(int n) [[ pre: n > 1 ]] override [[ pre: n > 0 ]] { return -n; } // { dg-error "contracts must appertain" } + int f12(int n) [[ pre: n > 0 ]] override [[ pre: n > 1 ]] { return -n; } // { dg-error "contracts must appertain" } +}; + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-post1.C b/gcc/testsuite/g++.dg/contracts/contracts-post1.C new file mode 100644 index 00000000000..451760ea822 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-post1.C @@ -0,0 +1,74 @@ +// { dg-do compile } +// { dg-options "-std=c++2a -fcontracts" } + +int f1(int n) + [[pre: n >= 0]] + [[post r: r >= 0]] + [[post r: !(r < 0)]] +{ + return n; +} + +int f2(int n) + [[post: true]] +{ + return 0; +} + +int f3(int n) + [[post r: r >= n]] +{ + return n + 1; +} + +int f4(int n) + [[post: x > 0]] // { dg-error "not declared" } +{ + return 0; +} + +void f5() + [[post: true]] +{ } + +void f6() + [[post r: true]] // { dg-error "function does not return a value" } +{ } + +int f7(int n) + [[post: n > 0]] +{ + return x; // { dg-error "not declared" } +} + +void f8(int n) + [[post: n > 0]] +{ + return; +} + +void f9(int n) + [[post: n > 0]] +{ + return n; // { dg-error "return-statement with a value" } +} + +int f10(int n) + [[post: n > 0]] +{ + return; // { dg-error "return-statement with no value" } +} + +void f11() + [[post: true]] +{ + constexpr int n = 0; + return n; // { dg-error "return-statement with a value" } +} + +int f12() + [[post: true]] +{ + return; // { dg-error "return-statement with no value" } +} + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-post2.C b/gcc/testsuite/g++.dg/contracts/contracts-post2.C new file mode 100644 index 00000000000..7665f829107 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-post2.C @@ -0,0 +1,13 @@ +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts" } + +int f1(int n) + [[post r: r == n]] +{ + return n; +} + +int main() +{ + f1(0); +} diff --git a/gcc/testsuite/g++.dg/contracts/contracts-post3.C b/gcc/testsuite/g++.dg/contracts/contracts-post3.C new file mode 100644 index 00000000000..9f1dffd8f6a --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-post3.C @@ -0,0 +1,15 @@ +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts" } +// { dg-shouldfail "assert violation" } +// { dg-output "default std::handle_contract_violation called" } + +int f1(int n) + [[post r: r > n]] +{ + return n; +} + +int main() +{ + f1(0); +} diff --git a/gcc/testsuite/g++.dg/contracts/contracts-post4.C b/gcc/testsuite/g++.dg/contracts/contracts-post4.C new file mode 100644 index 00000000000..af770c3dc94 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-post4.C @@ -0,0 +1,36 @@ +// { dg-do compile } +// { dg-options "-std=c++2a -fcontracts" } + +struct S +{ + S() [[post: n == 0]] + : n(0) + { } + + ~S() [[post: true]] + { } + + int f1() + [[post r: n == r]] + { + return n; + } + + int f2() + [[post r: r == x]] // { dg-error "not declared" } + { + return n; + } + + void f3() + [[post r: n]] // { dg-error "does not return a value" } + { + } + + int n = 0; +}; + +int main() +{ + // f1(0); +} diff --git a/gcc/testsuite/g++.dg/contracts/contracts-post5.C b/gcc/testsuite/g++.dg/contracts/contracts-post5.C new file mode 100644 index 00000000000..c9127fb2cc9 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-post5.C @@ -0,0 +1,19 @@ +// { dg-do compile } +// { dg-options "-std=c++2a -fcontracts" } + +// Tests for function templates + +template +T f1(T n) + [[pre: n >= 0]] + [[post r: r >= 0]] + [[post r: !(r < 0)]] +{ + return n; +} + + +void driver() +{ + f1(0); +} \ No newline at end of file diff --git a/gcc/testsuite/g++.dg/contracts/contracts-post6.C b/gcc/testsuite/g++.dg/contracts/contracts-post6.C new file mode 100644 index 00000000000..f8246fbc15f --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-post6.C @@ -0,0 +1,30 @@ +// { dg-do compile } +// { dg-options "-std=c++2a -fcontracts" } + +// Test for class members + +template +struct S +{ + S(T n) + [[post: true]] + : n(n) + { } + + T f1(T n) + [[pre: n >= 0]] + [[post r: r >= 0]] + [[post r: !(r < 0)]] + { + return n; + } + + T n; +}; + + +void driver() +{ + S s1(0); + s1.f1(2); +} diff --git a/gcc/testsuite/g++.dg/contracts/contracts-pre1.C b/gcc/testsuite/g++.dg/contracts/contracts-pre1.C new file mode 100644 index 00000000000..908103cde1e --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-pre1.C @@ -0,0 +1,36 @@ +// generic pre contract parsing checks +// check omitted, 'default', 'audit', and 'axiom' contract levels parse +// ensure that an invalid contrcat level 'off' errors +// ensure that a predicate referencing an undefined variable errors +// ensure that a missing colon after contract level errors +// { dg-do compile } +// { dg-options "-std=c++2a -fcontracts" } + +void f1(int x) [[ pre: x >= 0 ]] { } +void f2(int x) [[ pre default: x >= 0 ]] { } +void f3(int x) [[ pre audit: x >= 0 ]] { } +void f4(int x) [[ pre axiom: x >= 0 ]] { } + +void finvalid(int x) [[ pre invalid: x >= 0 ]] { } // { dg-error "expected contract level" } +void fundeclared() [[ pre: x >= 0 ]] { } // { dg-error ".x. was not declared in this scope" } +void fmissingcolon(int x) [[ pre default x == 0]] { } // { dg-error "expected .:. before .x." } + +int Z; +void (*fp1)(int x) [[ pre: Z > 0 ]]; // { dg-error "contracts must appertain" } +void (*fp2 [[ pre: Z > 0 ]])(int x); // { dg-error "contracts must appertain" } +typedef void (*fp3)(int x) [[ pre: Z > 0 ]]; // { dg-error "contracts must appertain" } +typedef void (*fp4 [[ pre: Z > 0 ]])(int x); // { dg-error "contracts must appertain" } +fp3 fn5(int a) [[ pre: a > 0 ]]; // { dg-bogus "contracts must appertain" } + +int xyz; +[[ pre: xyz ]] struct Bar; // { dg-error "contracts must appertain" } +// { dg-warning "attribute ignored" "" { target *-*-* } .-1 } +struct [[ pre: xyz ]] Bar; // { dg-error "contracts must appertain" } +struct Bar [[ pre: xyz ]]; // { dg-error "contracts must appertain" } +struct Zoo {} x [[ pre: xyz ]]; // { dg-error "contracts must appertain" } + +void f6(int x) [[ pre: x > 0 ; // { dg-error "expected .]." } +void f7(int x) [[ pre: x > 0 ]; // { dg-error "expected .]." } +void f8(int x) [[ pre: x > 0 { }; // { dg-error "expected .]." } +void f9(int x) [[ pre: x > 0 ] { }; // { dg-error "expected .]." } + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-pre10.C b/gcc/testsuite/g++.dg/contracts/contracts-pre10.C new file mode 100644 index 00000000000..877e9ada404 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-pre10.C @@ -0,0 +1,190 @@ +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts -fcontract-continuation-mode=on" } + +struct M +{ + template + int f(int a) [[ pre: a > 0 ]]; + + template + int g(int a) [[ pre: a > 0 ]] + { + return -a; + } + + template + int f_arg(T a) [[ pre: a > 0 ]]; + + template + int g_arg(T a) [[ pre: a > 0 ]] + { + return (int)-a; + } + + template + T f_ret(int a) [[ pre: a > 0 ]]; + + template + T g_ret(int a) [[ pre: a > 0 ]] + { + return -a * 1.5; + } +}; + +template +int M::f(int a) +{ + return -a; +} + +template +int M::f_arg(T a) +{ + return (int)-a; +} + +template +T M::f_ret(int a) +{ + return -a * (T)1.5; +} + +template +struct S +{ + template + int f(int a) [[ pre: a > 0 ]]; + + template + int g(int a) [[ pre: a > 0 ]] + { + return -a; + } +}; + +template +template +int S::f(int a) +{ + return -a; +} + +#include +int main(int, char**) +{ + { + M m; + printf ("=================================\n"); + printf ("m.f(-10): %d\n", m.f(-10)); + printf ("m.f(-11.5): %d\n", m.f(-11.5)); + printf ("m.f(10): %d\n", m.f(10)); + printf ("m.f(11.5): %d\n", m.f(11.5)); + + printf ("=================================\n"); + printf ("m.g(-10): %d\n", m.g(-10)); + printf ("m.g(-11.5): %d\n", m.g(-11.5)); + printf ("m.g(10): %d\n", m.g(10)); + printf ("m.g(11.5): %d\n", m.g(11.5)); + + printf ("=================================\n"); + printf ("m.f_arg(-10): %d\n", m.f_arg(-10)); + printf ("m.f_arg(-11.5): %d\n", m.f_arg(-11.5)); + printf ("m.f_arg(10): %d\n", m.f_arg(10)); + printf ("m.f_arg(11.5): %d\n", m.f_arg(11.5)); + + printf ("=================================\n"); + printf ("m.g_arg(-10): %d\n", m.g_arg(-10)); + printf ("m.g_arg(-11.5): %d\n", m.g_arg(-11.5)); + printf ("m.g_arg(10): %d\n", m.g_arg(10)); + printf ("m.g_arg(11.5): %d\n", m.g_arg(11.5)); + + printf ("=================================\n"); + printf ("m.f_ret(-10): %d\n", m.f_ret(-10)); + printf ("m.f_ret(-11.5): %f\n", m.f_ret(-11.5)); + printf ("m.f_ret(10): %d\n", m.f_ret(10)); + printf ("m.f_ret(11.5): %f\n", m.f_ret(11.5)); + + printf ("=================================\n"); + printf ("m.g_ret(-10): %d\n", m.g_ret(-10)); + printf ("m.g_ret(-11.5): %f\n", m.g_ret(-11.5)); + printf ("m.g_ret(10): %d\n", m.g_ret(10)); + printf ("m.g_ret(11.5): %f\n", m.g_ret(11.5)); + } + + { + S s; + printf ("=================================\n"); + s.f(-10); + + s.f(-10); + + printf ("=================================\n"); + s.g(-10); + + s.g(-10); + } + + { + S s; + printf ("=================================\n"); + s.f(-10); + + s.f(-10); + + printf ("=================================\n"); + s.g(-10); + + s.g(-10); + } + + return 0; +} + +// { dg-output "=================================(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 7 M::f .*(\n|\r\n|\r)*" } +// { dg-output "m.f.-10.: 10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 7 M::f .*(\n|\r\n|\r)*" } +// { dg-output "m.f.-11.5.: 11(\n|\r\n|\r)*" } +// { dg-output "m.f.10.: -10(\n|\r\n|\r)*" } +// { dg-output "m.f.11.5.: -11(\n|\r\n|\r)*" } +// { dg-output "=================================(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 10 M::g .*(\n|\r\n|\r)*" } +// { dg-output "m.g.-10.: 10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 10 M::g .*(\n|\r\n|\r)*" } +// { dg-output "m.g.-11.5.: 11(\n|\r\n|\r)*" } +// { dg-output "m.g.10.: -10(\n|\r\n|\r)*" } +// { dg-output "m.g.11.5.: -11(\n|\r\n|\r)*" } +// { dg-output "=================================(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 16 M::f_arg .*(\n|\r\n|\r)*" } +// { dg-output "m.f_arg.-10.: 10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 16 M::f_arg .*(\n|\r\n|\r)*" } +// { dg-output "m.f_arg.-11.5.: 11(\n|\r\n|\r)*" } +// { dg-output "m.f_arg.10.: -10(\n|\r\n|\r)*" } +// { dg-output "m.f_arg.11.5.: -11(\n|\r\n|\r)*" } +// { dg-output "=================================(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 19 M::g_arg .*(\n|\r\n|\r)*" } +// { dg-output "m.g_arg.-10.: 10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 19 M::g_arg .*(\n|\r\n|\r)*" } +// { dg-output "m.g_arg.-11.5.: 11(\n|\r\n|\r)*" } +// { dg-output "m.g_arg.10.: -10(\n|\r\n|\r)*" } +// { dg-output "m.g_arg.11.5.: -11(\n|\r\n|\r)*" } +// { dg-output "=================================(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 25 M::f_ret .*(\n|\r\n|\r)*" } +// { dg-output "m.f_ret.-10.: 10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 25 M::f_ret .*(\n|\r\n|\r)*" } +// { dg-output "m.f_ret.-11.5.: 16.500000(\n|\r\n|\r)*" } +// { dg-output "m.f_ret.10.: -10(\n|\r\n|\r)*" } +// { dg-output "m.f_ret.11.5.: -16.500000(\n|\r\n|\r)*" } +// { dg-output "=================================(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 28 M::g_ret .*(\n|\r\n|\r)*" } +// { dg-output "m.g_ret.-10.: 15(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 28 M::g_ret .*(\n|\r\n|\r)*" } +// { dg-output "m.g_ret.-11.5.: 16.500000(\n|\r\n|\r)*" } +// { dg-output "m.g_ret.10.: -15(\n|\r\n|\r)*" } +// { dg-output "m.g_ret.11.5.: -16.500000(\n|\r\n|\r)*" } +// { dg-output "=================================(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 59 S::g .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 59 S::g .*(\n|\r\n|\r)*" } +// { dg-output "=================================(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 59 S::g .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 59 S::g .*(\n|\r\n|\r)*" } diff --git a/gcc/testsuite/g++.dg/contracts/contracts-pre2.C b/gcc/testsuite/g++.dg/contracts/contracts-pre2.C new file mode 100644 index 00000000000..4fe2f9d0192 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-pre2.C @@ -0,0 +1,212 @@ +// basic test to ensure pre contracts work for free functions +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts -fcontract-continuation-mode=on" } +#include + +namespace nullary +{ + int x = 10; + int y = 10; + + void fun() + [[ pre: x < 0 ]] + { + printf("fun::x: %d\n", x); + } + + void fun2() + [[ pre: x < 0 ]] + [[ pre: y < 0 ]] + { + printf("fun2::x: %d fun2::y: %d\n", x, y); + } + + void funend() + [[ pre: x < 0 ]]; +} + +namespace nonvoid +{ + int x = 10; + double y = 10.5; + + struct S + { + bool z; + }; + + void vfun() + [[ pre: x < 0 ]] + { + printf("vfun::x: %d\n", x); + } + + int fun() + [[ pre: x < 0 ]] + { + printf("fun::x: %d\n", x); + return x; + } + + double fun2() + [[ pre: x < 0 ]] + [[ pre: y < 0 ]] + { + printf("fun2::x: %d fun2::y: %f\n", x, y); + return y; + } + + S funend() + [[ pre: x < 0 ]]; +} + +namespace nonnullary +{ + int x = 10; + double y = 10.5; + + struct S + { + bool z; + }; + + void vfun(int m, double n) + [[ pre: x < 0 ]] + [[ pre: m < 0 ]] + { + printf("vfun::x: %d\n", x); + } + + int fun(int m, double n) + [[ pre: x < 0 ]] + { + printf("fun::x: %d\n", x); + return x; + } + + double fun2(int m, double n) + [[ pre: x < 0 ]] + [[ pre: y < 0 ]] + [[ pre: m < 0 ]] + [[ pre: n < 0 ]] + { + printf("fun2::x: %d fun2::y: %f\n", x, y); + return y; + } + + S funend(int m, double n) + [[ pre: x < 0 ]] + [[ pre: m < 0 ]]; +} + +int main(int, char**) { + // nullary void + { + nullary::fun(); + nullary::fun2(); + nullary::funend(); + } + + // nullary non void + { + nonvoid::vfun(); + + int f = 13; + f = nonvoid::fun(); + printf("main::f: %d\n", f); + double d = 13.37; + d = nonvoid::fun2(); + printf("main::d: %f\n", d); + nonvoid::S s = nonvoid::funend(); + printf("main::s.z: %d\n", s.z ? 1 : 0); + } + + // non-nullary non-void + { + int x = 11; + double y = 11.5; + + nonnullary::vfun(x, y); + + int f = 13; + f = nonnullary::fun(x, y); + printf("main::f: %d\n", f); + double d = 13.37; + d = nonnullary::fun2(x, y); + printf("main::d: %f\n", d); + nonnullary::S s = nonnullary::funend(x, y); + printf("main::s.z: %d\n", s.z ? 1 : 0); + } + return 0; +} + +namespace nullary +{ + void funend() + [[ pre: x < 0 ]] + { + printf("funend::x: %d\n", x); + } +} + +namespace nonvoid +{ + S funend() + [[ pre: x < 0 ]] + { + printf("funend::x: %d\n", x); + S s; + s.z = true; + return s; + } +} + +namespace nonnullary +{ + S funend(int m, double n) + [[ pre: x < 0 ]] + [[ pre: m < 0 ]] + { + printf("funend::x: %d\n", x); + S s; + s.z = true; + return s; + } +} + +// { dg-output "default std::handle_contract_violation called: .*.C 12 nullary::fun .*(\n|\r\n|\r)*" } +// { dg-output "fun::x: 10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 18 nullary::fun2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 19 nullary::fun2 .*(\n|\r\n|\r)*" } +// { dg-output "fun2::x: 10 fun2::y: 10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 146 nullary::funend .*(\n|\r\n|\r)*" } +// { dg-output "funend::x: 10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 39 nonvoid::vfun .*(\n|\r\n|\r)*" } +// { dg-output "vfun::x: 10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 45 nonvoid::fun .*(\n|\r\n|\r)*" } +// { dg-output "fun::x: 10(\n|\r\n|\r)*" } +// { dg-output "main::f: 10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 52 nonvoid::fun2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 53 nonvoid::fun2 .*(\n|\r\n|\r)*" } +// { dg-output "fun2::x: 10 fun2::y: 10.500000(\n|\r\n|\r)*" } +// { dg-output "main::d: 10.500000(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 155 nonvoid::funend .*(\n|\r\n|\r)*" } +// { dg-output "funend::x: 10(\n|\r\n|\r)*" } +// { dg-output "main::s.z: 1(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 74 nonnullary::vfun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 75 nonnullary::vfun .*(\n|\r\n|\r)*" } +// { dg-output "vfun::x: 10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 81 nonnullary::fun .*(\n|\r\n|\r)*" } +// { dg-output "fun::x: 10(\n|\r\n|\r)*" } +// { dg-output "main::f: 10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 88 nonnullary::fun2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 89 nonnullary::fun2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 90 nonnullary::fun2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 91 nonnullary::fun2 .*(\n|\r\n|\r)*" } +// { dg-output "fun2::x: 10 fun2::y: 10.500000(\n|\r\n|\r)*" } +// { dg-output "main::d: 10.500000(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 167 nonnullary::funend .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 168 nonnullary::funend .*(\n|\r\n|\r)*" } +// { dg-output "funend::x: 10(\n|\r\n|\r)*" } +// { dg-output "main::s.z: 1(\n|\r\n|\r)*" } + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-pre2a1.C b/gcc/testsuite/g++.dg/contracts/contracts-pre2a1.C new file mode 100644 index 00000000000..26167492b97 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-pre2a1.C @@ -0,0 +1,33 @@ +// ensure the feature test macros are defined pre c++20 while we still support +// -fcontracts independent of std version +// { dg-do compile { target c++11 } } +// { dg-additional-options "-fcontracts" } + +static_assert (__cpp_contracts >= 201906, "__cpp_contracts"); +static_assert (__cpp_contracts_literal_semantics >= 201906, "__cpp_contracts_literal_semantics"); +static_assert (__cpp_contracts_roles >= 201906, "__cpp_contracts_roles"); + +int main() +{ + int x; + + [[assert: x >= 0]]; + [[assert default: x < 0]]; + [[assert audit: x == 0]]; + [[assert axiom: x == 1]]; + + [[assert: x > 0 ? true : false]]; + [[assert: x < 0 ? true : false]]; + + [[assert ignore: x >= 0]]; + [[assert assume: x >= 0]]; + [[assert check_never_continue: x >= 0]]; + [[assert check_maybe_continue: x >= 0]]; + + [[assert %default: x >= 0]]; + [[assert default %default: x < 0]]; + [[assert audit %default: x == 0]]; + [[assert axiom %default: x == 1]]; + return 0; +} + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-pre2a2.C b/gcc/testsuite/g++.dg/contracts/contracts-pre2a2.C new file mode 100644 index 00000000000..db9a0c37aa0 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-pre2a2.C @@ -0,0 +1,22 @@ +// basic test to ensure contracts work pre-c++2a +// { dg-do run { target c++11 } } +// { dg-additional-options "-fcontracts -fcontract-continuation-mode=on" } + +int f(int n) + [[ pre: n > 0 ]] + [[ post r: r < 0 ]] +{ + [[ assert: n > 0 ]]; + return -n; +} + +int main() +{ + f(-5); + return 0; +} + +// { dg-output "default std::handle_contract_violation called: .*.C 6 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 9 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 7 .*(\n|\r\n|\r)*" } + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-pre3.C b/gcc/testsuite/g++.dg/contracts/contracts-pre3.C new file mode 100644 index 00000000000..1c4e3a98e63 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-pre3.C @@ -0,0 +1,525 @@ +// tests to ensure pre contracts work on member functions +// { dg-do compile } +// { dg-options "-std=c++2a -fcontracts -fcontract-continuation-mode=on" } +#include + +namespace member +{ + int x = 10; + double y = 10.5; + + struct S + { + bool z; + }; + + struct T1 + { + void vfun(int m, double n) + [[ pre: x < 0 ]] + [[ pre: m < 0 ]]; + + int fun(int m, double n) + [[ pre: x < 0 ]]; + + double fun2(int m, double n) + [[ pre: x < 0 ]] + [[ pre: y < 0 ]] + [[ pre: m < 0 ]] + [[ pre: n < 0 ]]; + + S funend(int m, double n) + [[ pre: x < 0 ]] + [[ pre: m < 0 ]]; + }; + + void T1::vfun(int m, double n) + [[ pre: x < 0 ]] + [[ pre: m < 0 ]] + { + printf("vfun::x: %d\n", x); + } + + + int T1::fun(int m, double n) + [[ pre: x < 0 ]] + { + printf("fun::x: %d\n", x); + return x; + } + + double T1::fun2(int m, double n) + [[ pre: x < 0 ]] + [[ pre: y < 0 ]] + [[ pre: m < 0 ]] + [[ pre: n < 0 ]] + { + printf("fun2::x: %d fun2::y: %f\n", x, y); + return y; + } +} + +namespace special +{ + struct T1 + { + T1(int m, int n) + [[ pre: m < 0 ]] + [[ pre: n < 0 ]]; + + int operator+(int m) + [[ pre: m > 0 ]] + [[ pre: v > 0 ]]; + + int operator-(int m) + [[ pre: m > 0 ]] + [[ pre: v > 0 ]]; + + ~T1() + [[ pre: v > 0 ]]; + + int v{-10}; + }; + + T1::T1(int m, int n) + [[ pre: m < 0 ]] + [[ pre: n < 0 ]] + : v{-m * n} + { + } + + int T1::operator+(int m) + [[ pre: m > 0 ]] + [[ pre: v > 0 ]] + { + return v + m; + } + + int T1::operator-(int m) + [[ pre: m > 0 ]] + [[ pre: v > 0 ]] + { + return v - m; + } + + T1::~T1() + [[ pre: v > 0 ]] + { + } + + struct T2 + { + T2(int m, int n) + [[ pre: m < 0 ]] + [[ pre: n < 0 ]] + : v{-m * n} + { + } + + int operator+(int m) + [[ pre: m > 0 ]] + [[ pre: v > 0 ]] + { + return v + m; + } + + int operator-(int m) + [[ pre: m > 0 ]] + [[ pre: v > 0 ]] + { + return v - m; + } + + ~T2() + [[ pre: v > 0 ]] + { + } + + int v{-10}; + }; + + struct TC : T1 + { + TC(int m, int n) + [[ pre: m < -1 ]] + [[ pre: n < -1 ]] + : T1{m, n} + { + } + + ~TC() + [[ pre: vc < 0 ]] + { + } + + TC(int a) + [[ pre: a < 0 ]] + : TC{a, a} + { + } + + int vc{10}; + }; + + void test() + { + T1 t1{10, 20}; + int a = t1 - -5; + int b = t1 + -5; + printf("==========\n"); + + T2 t2{10, 20}; + int k = t2 - -5; + int j = t2 + -5; + printf("==========\n"); + + TC tc{10, 20}; + printf("==========\n"); + + TC tc2{10}; + printf("==========\n"); + } +} + +namespace virt +{ + struct T1 + { + virtual int fun(int m, int n) + [[ pre: m > 0 ]] + [[ pre: n > 0 ]] + [[ pre: v > 0 ]]; + int v{-10}; + }; + + int T1::fun(int m, int n) + [[ pre: m > 0 ]] + [[ pre: n > 0 ]] + [[ pre: v > 0 ]] + { + printf("T1::fun::m: %d, T1::fun::n: %d, T1::v: %d\n", m, n, v); + return m * n * v; + } + + struct T2 : T1 + { + }; + + struct T3 : T2 + { + virtual int fun(int m, int n) + [[ pre: m > 0 ]] + [[ pre: n > 0 ]] + [[ pre: v > 0 ]] + override + { + printf("T3::fun::m: %d, T3::fun::n: %d, T3::v: %d\n", m, n, v); + return m * n * v; + } + }; + + struct T3b : T2 + { + virtual int fun(int m, int n) + [[ pre: m > 0 ]] + [[ pre: n > 0 ]] + [[ pre: v > 0 ]] + override; + + int p(int a) + [[ pre: a > 0 ]] + [[ pre: v > 0 ]]; + + int u(int a) + [[ pre: a > 0 ]] + [[ pre: z > 0 ]]; + + int n(int a) + [[ pre: a > 0 ]]; + + static int Sn(int a) + [[ pre: a > 0 ]]; + + int z{-10}; + }; + + int T3b::fun(int m, int n) + [[ pre: m > 0 ]] + [[ pre: n > 0 ]] + [[ pre: v > 0 ]] + { + printf("T3b::fun::m: %d, T3b::fun::n: %d, T3b::v: %d\n", m, n, v); + return m * n * v; + } + + int T3b::p(int a) + [[ pre: a > 0 ]] + [[ pre: v > 0 ]] + { + printf("T3b::p: a: %d, v: %d\n", a, v); + return a * v; + } + + int T3b::u(int a) + [[ pre: a > 0 ]] + [[ pre: z > 0 ]] + { + printf("T3b::u: a: %d, z: %d\n", a, z); + return a * z; + } + + int T3b::n(int a) + [[ pre: a > 0 ]] + { + printf("T3b::n: a: %d\n", a); + return a; + } + + int T3b::Sn(int a) + [[ pre: a > 0 ]] + { + printf("T3b::Sn: a: %d\n", a); + return a; + } + + struct T3c : T2 + { + int fun(int m, int n) + [[ pre: m > 0 ]] + [[ pre: n > 0 ]] + [[ pre: v > 0 ]] + { + printf("T3c::fun::m: %d, T3c::fun::n: %d, T3c::v: %d\n", m, n, v); + return m * n * v; + } + + int p(int a) + [[ pre: a > 0 ]] + [[ pre: v > 0 ]] + { + printf("T3c::p: a: %d, v: %d\n", a, v); + return a * v; + } + + int u(int a) + [[ pre: a > 0 ]] + [[ pre: z > 0 ]] + { + printf("T3c::u: a: %d, z: %d\n", a, z); + return a * z; + } + + int n(int a) + [[ pre: a > 0 ]] + { + printf("T3c::n: a: %d\n", a); + return a; + } + + static int Sn(int a) + [[ pre: a > 0 ]] + { + printf("T3c::Sn: a: %d\n", a); + return a; + } + + int z{-10}; + }; + + void t(const char *kind, T1 *t) + { + printf("=================\n%s:\n", kind); + t->fun(-1, -2); + } + + void test() + { + T1 t1; + t1.fun(-10, -20); + + T2 t2; + t2.fun(-10, -20); + + T3 t3; + t3.fun(-10, -20); + + T3b t3b; + t3b.fun(-10, -20); + + T3c t3c; + t3c.fun(-10, -20); + + t("T1", &t1); + t("T2", &t2); + t("T3", &t3); + t("T3b", &t3b); + t("T3c", &t3c); + + printf("=============\n"); + t3b.p(-3); + t3b.u(-3); + t3b.n(-3); + T3b::Sn(-3); + + printf("=============\n"); + t3c.p(-3); + t3c.u(-3); + t3c.n(-3); + T3c::Sn(-3); + } +} + +int main(int, char**) +{ + // ordinary member functions + { + int x = 11; + double y = 11.5; + member::T1 t1; + t1.vfun(x, y); + + int f = 13; + f = t1.fun(x, y); + printf("main::f: %d\n", f); + double d = 13.37; + d = t1.fun2(x, y); + printf("main::d: %f\n", d); + member::S s = t1.funend(x, y); + printf("main::s.z: %d\n", s.z ? 1 : 0); + } + + special::test(); + virt::test(); + return 0; +} + +member::S member::T1::funend(int m, double n) + [[ pre: x < 0 ]] + [[ pre: m < 0 ]] +{ + printf("funend::x: %d\n", x); + S s; + s.z = true; + return s; +} + +// { dg-output "default std::handle_contract_violation called: .*.C 37 member::T1::vfun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 38 member::T1::vfun .*(\n|\r\n|\r)*" } +// { dg-output "vfun::x: 10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 45 member::T1::fun .*(\n|\r\n|\r)*" } +// { dg-output "fun::x: 10(\n|\r\n|\r)*" } +// { dg-output "main::f: 10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 52 member::T1::fun2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 53 member::T1::fun2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 54 member::T1::fun2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 55 member::T1::fun2 .*(\n|\r\n|\r)*" } +// { dg-output "fun2::x: 10 fun2::y: 10.500000(\n|\r\n|\r)*" } +// { dg-output "main::d: 10.500000(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 397 member::T1::funend .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 398 member::T1::funend .*(\n|\r\n|\r)*" } +// { dg-output "funend::x: 10(\n|\r\n|\r)*" } +// { dg-output "main::s.z: 1(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 85 special::T1::T1 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 86 special::T1::T1 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 99 special::T1::operator- .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 100 special::T1::operator- .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 92 special::T1::operator. .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 93 special::T1::operator. .*(\n|\r\n|\r)*" } +// { dg-output "==========(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 113 special::T2::T2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 114 special::T2::T2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 127 special::T2::operator- .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 128 special::T2::operator- .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 120 special::T2::operator. .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 121 special::T2::operator. .*(\n|\r\n|\r)*" } +// { dg-output "==========(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 144 special::TC::TC .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 145 special::TC::TC .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 85 special::T1::T1 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 86 special::T1::T1 .*(\n|\r\n|\r)*" } +// { dg-output "==========(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 156 special::TC::TC .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 144 special::TC::TC .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 145 special::TC::TC .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 85 special::T1::T1 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 86 special::T1::T1 .*(\n|\r\n|\r)*" } +// { dg-output "==========(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 151 special::TC::~TC .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 106 special::T1::~T1 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 151 special::TC::~TC .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 106 special::T1::~T1 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 134 special::T2::~T2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 106 special::T1::~T1 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 196 virt::T1::fun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 197 virt::T1::fun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 198 virt::T1::fun .*(\n|\r\n|\r)*" } +// { dg-output "T1::fun::m: -10, T1::fun::n: -20, T1::v: -10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 196 virt::T1::fun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 197 virt::T1::fun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 198 virt::T1::fun .*(\n|\r\n|\r)*" } +// { dg-output "T1::fun::m: -10, T1::fun::n: -20, T1::v: -10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 211 virt::T3::fun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 212 virt::T3::fun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 213 virt::T3::fun .*(\n|\r\n|\r)*" } +// { dg-output "T3::fun::m: -10, T3::fun::n: -20, T3::v: -10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 247 virt::T3b::fun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 248 virt::T3b::fun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 249 virt::T3b::fun .*(\n|\r\n|\r)*" } +// { dg-output "T3b::fun::m: -10, T3b::fun::n: -20, T3b::v: -10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 288 virt::T3c::fun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 289 virt::T3c::fun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 290 virt::T3c::fun .*(\n|\r\n|\r)*" } +// { dg-output "T3c::fun::m: -10, T3c::fun::n: -20, T3c::v: -10(\n|\r\n|\r)*" } +// { dg-output "=================(\n|\r\n|\r)*" } +// { dg-output "T1:(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 196 virt::T1::fun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 197 virt::T1::fun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 198 virt::T1::fun .*(\n|\r\n|\r)*" } +// { dg-output "T1::fun::m: -1, T1::fun::n: -2, T1::v: -10(\n|\r\n|\r)*" } +// { dg-output "=================(\n|\r\n|\r)*" } +// { dg-output "T2:(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 196 virt::T1::fun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 197 virt::T1::fun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 198 virt::T1::fun .*(\n|\r\n|\r)*" } +// { dg-output "T1::fun::m: -1, T1::fun::n: -2, T1::v: -10(\n|\r\n|\r)*" } +// { dg-output "=================(\n|\r\n|\r)*" } +// { dg-output "T3:(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 211 virt::T3::fun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 212 virt::T3::fun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 213 virt::T3::fun .*(\n|\r\n|\r)*" } +// { dg-output "T3::fun::m: -1, T3::fun::n: -2, T3::v: -10(\n|\r\n|\r)*" } +// { dg-output "=================(\n|\r\n|\r)*" } +// { dg-output "T3b:(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 247 virt::T3b::fun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 248 virt::T3b::fun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 249 virt::T3b::fun .*(\n|\r\n|\r)*" } +// { dg-output "T3b::fun::m: -1, T3b::fun::n: -2, T3b::v: -10(\n|\r\n|\r)*" } +// { dg-output "=================(\n|\r\n|\r)*" } +// { dg-output "T3c:(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 288 virt::T3c::fun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 289 virt::T3c::fun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 290 virt::T3c::fun .*(\n|\r\n|\r)*" } +// { dg-output "T3c::fun::m: -1, T3c::fun::n: -2, T3c::v: -10(\n|\r\n|\r)*" } +// { dg-output "=============(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 256 virt::T3b::p .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 257 virt::T3b::p .*(\n|\r\n|\r)*" } +// { dg-output "T3b::p: a: -3, v: -10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 264 virt::T3b::u .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 265 virt::T3b::u .*(\n|\r\n|\r)*" } +// { dg-output "T3b::u: a: -3, z: -10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 272 virt::T3b::n .*(\n|\r\n|\r)*" } +// { dg-output "T3b::n: a: -3(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 279 virt::T3b::Sn .*(\n|\r\n|\r)*" } +// { dg-output "T3b::Sn: a: -3(\n|\r\n|\r)*" } +// { dg-output "=============(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 297 virt::T3c::p .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 298 virt::T3c::p .*(\n|\r\n|\r)*" } +// { dg-output "T3c::p: a: -3, v: -10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 305 virt::T3c::u .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 306 virt::T3c::u .*(\n|\r\n|\r)*" } +// { dg-output "T3c::u: a: -3, z: -10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 313 virt::T3c::n .*(\n|\r\n|\r)*" } +// { dg-output "T3c::n: a: -3(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 320 virt::T3c::Sn .*(\n|\r\n|\r)*" } +// { dg-output "T3c::Sn: a: -3(\n|\r\n|\r)*" } + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-pre4.C b/gcc/testsuite/g++.dg/contracts/contracts-pre4.C new file mode 100644 index 00000000000..16189cdce9d --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-pre4.C @@ -0,0 +1,92 @@ +// test that contracts on overriding functions are found correctly +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts -fcontract-continuation-mode=on" } + +struct Base +{ + virtual int f(int a) [[ pre: a > 0 ]]; +}; + +int Base::f(int a) +{ + return a + 10; +} + +// inherits original +struct Child0 : Base +{ +}; + +// defined out of line, explicit override +struct Child1 : Base +{ + virtual int f(int a) override; +}; + +int Child1::f(int a) +{ + return a + 20; +} + +// defined out of line +struct Child2 : Base +{ + int f(int a); +}; + +int Child2::f(int a) +{ + return a + 30; +} + +// defined inline, explicitly override +struct Child3 : Base +{ + virtual int f(int a) override + { + return a + 40; + } +}; + +// defined inline +struct Child4 : Base +{ + int f(int a) + { + return a + 50; + } +}; + +#include +int main(int, char**) +{ + Base b; + Child0 c0; + Child1 c1; + Child2 c2; + Child3 c3; + Child4 c4; + + printf("Base: %d\n", b.f(-10)); + printf("Child0: %d\n", c0.f(-10)); + printf("Child1: %d\n", c1.f(-10)); + printf("Child2: %d\n", c2.f(-10)); + printf("Child3: %d\n", c3.f(-10)); + printf("Child4: %d\n", c4.f(-10)); + + return 0; +} + +// { dg-output "default std::handle_contract_violation called: .*.C 7 Base::f .*(\n|\r\n|\r)*" } +// { dg-output "Base: 0(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 7 Base::f .*(\n|\r\n|\r)*" } +// { dg-output "Child0: 0(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 7 Child1::f .*(\n|\r\n|\r)*" } +// { dg-output "Child1: 10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 7 Child2::f .*(\n|\r\n|\r)*" } +// { dg-output "Child2: 20(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 7 Child3::f .*(\n|\r\n|\r)*" } +// { dg-output "Child3: 30(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 7 Child4::f .*(\n|\r\n|\r)*" } +// { dg-output "Child4: 40(\n|\r\n|\r)*" } + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-pre5.C b/gcc/testsuite/g++.dg/contracts/contracts-pre5.C new file mode 100644 index 00000000000..278a545055f --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-pre5.C @@ -0,0 +1,81 @@ +// basic test to ensure pre contracts work for free templates +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts -fcontract-continuation-mode=on" } +#include + +template +int body(int a) + [[ pre: a > 0 ]] +{ + T t = a * 2.5; + return t; +} + +template +int none(int a) + [[ pre: a > 0 ]] +{ + return -a; +} + +template +int arg0(T t) + [[ pre: t > 0 ]] +{ + return -t - 10; +} + +template +int arg1(int a, T t) + [[ pre: a > 0 ]] + [[ pre: t > 0 ]] +{ + return -t * a; +} + +template +T ret(int a) + [[ pre: a > 0 ]] +{ + return -a; +} + +int main(int, char**) +{ + printf("%d\n", body(-1)); + printf("%d\n", body(-2)); + printf("%d\n", none(-1)); + printf("%d\n", none(-2)); + printf("%d\n", arg0(-1)); + printf("%d\n", arg0(-2.9)); + printf("%d\n", arg1(-3, -1)); + printf("%d\n", arg1(-4, -2.9)); + printf("%d\n", (int)ret(-3)); + printf("%d\n", (int)ret(-4.9)); + + return 0; +} + +// { dg-output "default std::handle_contract_violation called: .*.C 8 body .*(\n|\r\n|\r)*" } +// { dg-output "-2(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 8 body .*(\n|\r\n|\r)*" } +// { dg-output "-5(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 16 none .*(\n|\r\n|\r)*" } +// { dg-output "1(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 16 none .*(\n|\r\n|\r)*" } +// { dg-output "2(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 23 arg0 .*(\n|\r\n|\r)*" } +// { dg-output "-9(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 23 arg0 .*(\n|\r\n|\r)*" } +// { dg-output "-7(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 30 arg1 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 31 arg1 .*(\n|\r\n|\r)*" } +// { dg-output "-3(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 30 arg1 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 31 arg1 .*(\n|\r\n|\r)*" } +// { dg-output "-11(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 38 ret .*(\n|\r\n|\r)*" } +// { dg-output "3(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 38 ret .*(\n|\r\n|\r)*" } +// { dg-output "4(\n|\r\n|\r)*" } + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-pre6.C b/gcc/testsuite/g++.dg/contracts/contracts-pre6.C new file mode 100644 index 00000000000..44e8e264b54 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-pre6.C @@ -0,0 +1,74 @@ +// ensure no errors are thrown when we have to insert a decl for the internal +// unchecked function after leaving a (possibly nested) namespace +// { dg-do compile } +// { dg-options "-std=c++2a -fcontracts" } + +namespace ns0 +{ + int f(int a) [[ pre: a > 0 ]]; +} + +int ns0::f(int a) [[ pre: a > 0 ]] +{ + return -a; +} + +namespace ns0 +{ + namespace ns1 + { + int f(int a) [[ pre: a > 0 ]]; + } +} + +int ns0::ns1::f(int a) [[ pre: a > 0 ]] +{ + return -a; +} + +namespace ns0 +{ + namespace ns1 + { + int f2(int a) [[ pre: a > 0 ]]; + namespace ns2 + { + int f(int a) [[ pre: a > 0 ]]; + } + } + int ns1::f2(int a) [[ pre: a > 0 ]] + { + return -a; + } +} + +int ns0::ns1::ns2::f(int a) [[ pre: a > 0 ]] +{ + return -a; +} + +namespace ns0 +{ + struct S + { + int f(int a) [[ pre: a > 0 ]]; + }; + namespace ns1 + { + struct S2 + { + int f(int a) [[ pre: a > 0 ]]; + }; + } +} + +int ns0::S::f(int a) [[ pre: a > 0 ]] +{ + return -a; +} + +int ns0::ns1::S2::f(int a) [[ pre: a > 0 ]] +{ + return -a; +} + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-pre7.C b/gcc/testsuite/g++.dg/contracts/contracts-pre7.C new file mode 100644 index 00000000000..aeb8fc042e6 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-pre7.C @@ -0,0 +1,134 @@ +// ensure no errors are thrown when we have to insert a decl for the internal +// unchecked function after leaving a (possibly nested) namespace +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts -fcontract-continuation-mode=on" } + +namespace ns0 +{ + template + int f(T a) [[ pre: a > 0 ]]; +} + +template +int ns0::f(T a) [[ pre: a > 0 ]] +{ + return (int)-a; +} + +namespace ns0 +{ + namespace ns1 + { + template + int f(T a) [[ pre: a > 0 ]]; + } +} + +template +int ns0::ns1::f(T a) [[ pre: a > 0 ]] +{ + return -a; +} + +namespace ns0 +{ + namespace ns1 + { + template + int f2(T a) [[ pre: a > 0 ]]; + namespace ns2 + { + template + int f(T a) [[ pre: a > 0 ]]; + } + } + template + int ns1::f2(T a) [[ pre: a > 0 ]] + { + return -a; + } +} + +template +int ns0::ns1::ns2::f(T a) [[ pre: a > 0 ]] +{ + return -a; +} + +namespace ns0 +{ + template + struct S + { + int f(T a) [[ pre: a > 0 ]]; + }; + namespace ns1 + { + template + struct S2 + { + int f(T a) [[ pre: a > 0 ]]; + }; + } +} + +template +int ns0::S::f(T a) [[ pre: a > 0 ]] +{ + return -a; +} + +template +int ns0::ns1::S2::f(T a) [[ pre: a > 0 ]] +{ + return -a; +} + +#include +int main(int, char**) +{ + printf ("%d\n", ns0::f(-1)); + printf ("%d\n", ns0::ns1::f(-2)); + printf ("%d\n", ns0::ns1::f2(-3)); + printf ("%d\n", ns0::ns1::ns2::f(-4)); + ns0::S ns0_s; + printf ("%d\n", ns0_s.f(-5)); + ns0::ns1::S2 ns0_ns1_s2; + printf ("%d\n", ns0_ns1_s2.f(-6)); + + printf ("%d\n", ns0::f(-7.5)); + printf ("%d\n", ns0::ns1::f(-8.5)); + printf ("%d\n", ns0::ns1::f2(-9.5)); + printf ("%d\n", ns0::ns1::ns2::f(-10.5)); + ns0::S ns0_sd; + printf ("%d\n", ns0_sd.f(-11.5)); + ns0::ns1::S2 ns0_ns1_s2d; + printf ("%d\n", ns0_ns1_s2d.f(-12.5)); + + return 0; +} + +// { dg-output "default std::handle_contract_violation called: .*.C 13 ns0::f .*(\n|\r\n|\r)*" } +// { dg-output "1(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 28 ns0::ns1::f .*(\n|\r\n|\r)*" } +// { dg-output "2(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 46 ns0::ns1::f2 .*(\n|\r\n|\r)*" } +// { dg-output "3(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 53 ns0::ns1::ns2::f .*(\n|\r\n|\r)*" } +// { dg-output "4(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 76 ns0::S::f .*(\n|\r\n|\r)*" } +// { dg-output "5(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 82 ns0::ns1::S2::f .*(\n|\r\n|\r)*" } +// { dg-output "6(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 13 ns0::f .*(\n|\r\n|\r)*" } +// { dg-output "7(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 28 ns0::ns1::f .*(\n|\r\n|\r)*" } +// { dg-output "8(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 46 ns0::ns1::f2 .*(\n|\r\n|\r)*" } +// { dg-output "9(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 53 ns0::ns1::ns2::f .*(\n|\r\n|\r)*" } +// { dg-output "10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 76 ns0::S::f .*(\n|\r\n|\r)*" } +// { dg-output "11(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 82 ns0::ns1::S2::f .*(\n|\r\n|\r)*" } +// { dg-output "12(\n|\r\n|\r)*" } diff --git a/gcc/testsuite/g++.dg/contracts/contracts-pre9.C b/gcc/testsuite/g++.dg/contracts/contracts-pre9.C new file mode 100644 index 00000000000..64c0cfa36df --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-pre9.C @@ -0,0 +1,146 @@ +// ensure no errors are thrown for various combinations of class templates +// with guarded members +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts -fcontract-continuation-mode=on" } + +template +struct S +{ + int f(int a) [[ pre: a > 0 ]]; + int g(int a) [[ pre: a > 0 ]]; +}; + +template +int S::f(int a) [[ pre: a > 0 ]] +{ + return -a; +} + +template +int S::g(int a) // Contract is inherited (error from line 10). +{ + return -a; +} + +template +struct S_arg +{ + int f(T a) [[ pre: a > 0 ]]; + int g(T a) [[ pre: a > 0 ]]; +}; + +template +int S_arg::f(T a) [[ pre: a > 0 ]] +{ + return -a; +} + +template +int S_arg::g(T a) // Contract is inherited (error from line 29). +{ + return -a; +} + +template +struct S_ret +{ + T f(int a) [[ pre: a > 0 ]]; + T g(int a) [[ pre: a > 0 ]]; +}; + +template +T S_ret::f(int a) [[ pre: a > 0 ]] +{ + return -a; +} + +template +T S_ret::g(int a) // Contract is inherited (error from line 48). +{ + return -a; +} + +#include +int main(int, char**) +{ + { + S s_int; + printf ("s_int.f(-10): %d\n", s_int.f(-10)); + printf ("s_int.g(-10): %d\n", s_int.g(-10)); + printf ("s_int.f(10): %d\n", s_int.f(10)); + printf ("s_int.g(10): %d\n", s_int.g(10)); + + S s_double; + printf ("s_double.f(-10.5): %d\n", s_double.f(-10.5)); + printf ("s_double.g(-10.5): %d\n", s_double.g(-10.5)); + printf ("s_double.f(10.5): %d\n", s_double.f(10.5)); + printf ("s_double.g(10.5): %d\n", s_double.g(10.5)); + } + + { + S_arg s_arg_int; + printf ("s_arg_int.f(-10): %d\n", s_arg_int.f(-10)); + printf ("s_arg_int.g(-10): %d\n", s_arg_int.g(-10)); + printf ("s_arg_int.f(10): %d\n", s_arg_int.f(10)); + printf ("s_arg_int.g(10): %d\n", s_arg_int.g(10)); + + S_arg s_arg_double; + printf ("s_arg_double.f(-10): %d\n", s_arg_double.f(-10)); + printf ("s_arg_double.g(-10): %d\n", s_arg_double.g(-10)); + printf ("s_arg_double.f(10): %d\n", s_arg_double.f(10)); + printf ("s_arg_double.g(10): %d\n", s_arg_double.g(10)); + } + + { + S_ret s_ret_int; + printf ("s_ret_int.f(-10): %d\n", s_ret_int.f(-10)); + printf ("s_ret_int.g(-10): %d\n", s_ret_int.g(-10)); + printf ("s_ret_int.f(10): %d\n", s_ret_int.f(10)); + printf ("s_ret_int.g(10): %d\n", s_ret_int.g(10)); + + S_ret s_ret_double; + printf ("s_ret_double.f(-10): %f\n", s_ret_double.f(-10)); + printf ("s_ret_double.g(-10): %f\n", s_ret_double.g(-10)); + printf ("s_ret_double.f(10): %f\n", s_ret_double.f(10)); + printf ("s_ret_double.g(10): %f\n", s_ret_double.g(10)); + } + + return 0; +} + +// { dg-output "default std::handle_contract_violation called: .*.C 14 S::f .*(\n|\r\n|\r)*" } +// { dg-output "s_int.f.-10.: 10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 10 S::g .*(\n|\r\n|\r)*" } +// { dg-output "s_int.g.-10.: 10(\n|\r\n|\r)*" } +// { dg-output "s_int.f.10.: -10(\n|\r\n|\r)*" } +// { dg-output "s_int.g.10.: -10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 14 S::f .*(\n|\r\n|\r)*" } +// { dg-output "s_double.f.-10.5.: 10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 10 S::g .*(\n|\r\n|\r)*" } +// { dg-output "s_double.g.-10.5.: 10(\n|\r\n|\r)*" } +// { dg-output "s_double.f.10.5.: -10(\n|\r\n|\r)*" } +// { dg-output "s_double.g.10.5.: -10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 33 S_arg::f .*(\n|\r\n|\r)*" } +// { dg-output "s_arg_int.f.-10.: 10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 29 S_arg::g .*(\n|\r\n|\r)*" } +// { dg-output "s_arg_int.g.-10.: 10(\n|\r\n|\r)*" } +// { dg-output "s_arg_int.f.10.: -10(\n|\r\n|\r)*" } +// { dg-output "s_arg_int.g.10.: -10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 33 S_arg::f .*(\n|\r\n|\r)*" } +// { dg-output "s_arg_double.f.-10.: 10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 29 S_arg::g .*(\n|\r\n|\r)*" } +// { dg-output "s_arg_double.g.-10.: 10(\n|\r\n|\r)*" } +// { dg-output "s_arg_double.f.10.: -10(\n|\r\n|\r)*" } +// { dg-output "s_arg_double.g.10.: -10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 52 S_ret::f .*(\n|\r\n|\r)*" } +// { dg-output "s_ret_int.f.-10.: 10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 48 S_ret::g .*(\n|\r\n|\r)*" } +// { dg-output "s_ret_int.g.-10.: 10(\n|\r\n|\r)*" } +// { dg-output "s_ret_int.f.10.: -10(\n|\r\n|\r)*" } +// { dg-output "s_ret_int.g.10.: -10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 52 S_ret::f .*(\n|\r\n|\r)*" } +// { dg-output "s_ret_double.f.-10.: 10.000000(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 48 S_ret::g .*(\n|\r\n|\r)*" } +// { dg-output "s_ret_double.g.-10.: 10.000000(\n|\r\n|\r)*" } +// { dg-output "s_ret_double.f.10.: -10.000000(\n|\r\n|\r)*" } +// { dg-output "s_ret_double.g.10.: -10.000000(\n|\r\n|\r)*" } diff --git a/gcc/testsuite/g++.dg/contracts/contracts-redecl1.C b/gcc/testsuite/g++.dg/contracts/contracts-redecl1.C new file mode 100644 index 00000000000..58d0aafeff5 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-redecl1.C @@ -0,0 +1,149 @@ +// generic error tests for contract redecls with generalized redecl +// { dg-do compile } +// { dg-options "-std=c++2a -fcontracts" } + +// OK if equivalent -- even through renames. +int g0(int a) [[ pre: a > 0 ]]; +int g0(int a) [[ pre: a > 0 ]]; + +int g0b(int a) [[ pre: a > 0 ]]; +int g0b(int b) [[ pre: b > 0 ]]; +int g0b(int c) [[ pre: c > 0 ]] +{ + return 0; +} + +// OK if specified before. +int g1(int a) [[ pre: a > 0 ]]; +int g1(int a); + +// OK if specified after. +int g2(int a); +int g2(int a) [[ pre: a > 0 ]]; + +int g2b(int a); +int g2b(int b) [[ pre: b > 0 ]]; + +// can add to non-virtual methods +struct G0 +{ + int f(int a); +}; + +// OK to add contracts at the point of definition. +int G0::f(int a) [[ pre: a > 0 ]] +{ + return -a; +} + +struct G1 +{ + int f1(int a); +}; + +// OK to redeclare functions and add constraints... +int G1::f1(int a) [[ pre: a > 0 ]]; + +// ...and leave them off later. +int G1::f1(int a) +{ + return -a; +} + +int f0(int a) [[ pre: a > 0 ]]; +int f0(int a) [[ pre: a > 0 ]] [[ pre: a > 10 ]]; // { dg-error "different number of contracts" } + +int f1(int a) [[ pre: a > 0 ]] [[ pre: a > 10 ]]; +int f1(int a) [[ pre: a > 0 ]]; // { dg-error "different number of contracts" } + +int f2(int a) [[ pre: a > 0 ]]; +int f2(int a) [[ pre: a < 0 ]]; // { dg-error "mismatched contract" } + +int f3(int a) { return a; } +int f3(int a) [[ pre: a < 0 ]]; // { dg-error "cannot add contracts" } + +struct Base +{ + virtual int f(int a) [[ pre: a > 0 ]]; +}; + +struct Child : Base +{ + int f(int a) [[ pre: a < 0 ]]; // { dg-error "mismatched contract" } +}; + +struct S1 +{ + virtual int f(int a); // contracts are inherited at the point of declarations +}; + +int S1::f(int a) [[ pre: a > 0 ]] // { dg-error "cannot add" } +{ + return -a; +} + +struct S2 +{ + int f() { return 0; } +}; + +int S2::f(); // OK? + + +struct S3 +{ + int f() { return 0; } +}; + +int S3::f() [[pre: true]]; // { dg-error "cannot add contracts" } + + +// The initial decl of a guarded member must appear inside the class. +struct S4 +{ + int f(int a); +}; + +int S4::g(int a) [[ pre: a > 0 ]]; // { dg-error "no declaration matches" } + + +struct S5 +{ + template + S5(T a); +}; + +template +S5::S5(T a) [[ pre: a > 0 ]] +{ +} + +struct S6 +{ + template + S6(T a); +}; + +template +S6::S6(T a) [[ pre: a > 0 ]]; + +template +S6::S6(T a) +{ +} + +int p0(int n) + [[ post r: r > 0 && r == n ]] + [[ post r: r > 1 && r == n ]] + [[ post r: r > 2 && r == n ]] + [[ post r: r > 3 && r == n ]]; + +int p0(int z) + [[ post r: r > 0 && r == z ]] + [[ post r1: r1 > 1 && r1 == z ]] + [[ post r2: r2 > 2 && r2 == z ]] + [[ post r3: r3 > 3 && r3 == z ]] +{ + return z; +} + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-redecl2.C b/gcc/testsuite/g++.dg/contracts/contracts-redecl2.C new file mode 100644 index 00000000000..70c9259049f --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-redecl2.C @@ -0,0 +1,149 @@ +// generic error tests for generalized contract redecls +// { dg-do compile } +// { dg-options "-std=c++2a -fcontracts" } + +// allowed to repeat contracts or omit them +int g0(int a) [[ pre: a > 0 ]]; +int g0(int a) [[ pre: a > 0 ]]; + +int g1(int a) [[ pre: a > 0 ]]; +int g1(int a); + +// allowed to add from none if generalized redecl is on (by default) +int g2(int a); +int g2(int a) [[ pre: a > 0 ]]; + +// can add to non-virtual methods +struct G0 +{ + int f(int a); +}; + +int G0::f(int a) [[ pre: a > 0 ]] +{ + return -a; +} + +struct G1 +{ + int f(int a); +}; + +int G1::f(int a) [[ pre: a > 0 ]]; + +int G1::f(int a) +{ + return -a; +} + +// allowed to redeclare even without contracts +struct G2 +{ + int f(int a); +}; + +int G2::f(int a); + + +int f0(int a) [[ pre: a > 0 ]]; +int f0(int a) [[ pre: a > 0 ]] [[ pre: a > 10 ]]; // { dg-error "different number of contracts" } + +int f1(int a) [[ pre: a > 0 ]]; +int f1(int a) [[ pre: a < 0 ]]; // { dg-error "mismatched contract" } + +int f2(int a) { return a; } +int f2(int a) [[ pre: a < 0 ]]; // { dg-error "cannot add contracts after definition" } + +struct Base +{ + virtual int f(int a) [[ pre: a > 0 ]]; +}; + +struct Child : Base +{ + int f(int a) [[ pre: a < 0 ]]; // { dg-error "mismatched contract" } +}; + +// the initial decl of a guarded member must appear inside the class +struct F2 +{ + int f(int a); +}; + +int F2::g(int a) [[ pre: a > 0 ]]; // { dg-error "no declaration matches" } +// FIXME if we move F2 down then a different error makes F2 undeclared + +struct F0 +{ + virtual int f(int a); +}; + +int F0::f(int a); // { dg-error "declaration.*is not definition" } + +struct F1 +{ + virtual int f(int a); +}; + +int F1::f(int a) [[ pre: a > 0 ]] // { dg-error "cannot add" } +{ + return -a; +} + +// cannot "re"declare members of a forward declared class +struct F2; +int F2::test(); // { dg-error "no declaration matches" } +int F2::test2() [[ pre: true ]]; // { dg-error "no declaration matches" } + +// can only redeclare member functions +struct F3 +{ + int x; + typedef int my_int; + + struct Inner0; + struct Inner1; + enum my_enum0; // { dg-error "use of enum.*without previous decl" } + enum my_enum1 { E1, E2 }; + + int test0(); + int test1(); + int test2(); +}; + +int F3::x{-1}; // { dg-error "is not a static data member" } +typedef double F3::my_int; // { dg-error "typedef name may not be a nested-name-specifier" } +struct F3::Inner0; // { dg-warning "declaration.*does not declare anything" } + +struct F3::Inner1 { }; + +enum F3::my_enum1 { E0, E1, END }; // { dg-error "multiple definition" } + +struct F4 +{ + int test0(); + + int F3::test0() [[ pre: true ]]; // { dg-error "cannot declare member function" } + friend int F3::test1(); + friend int F3::test2(); +}; +int F3::test2() [[ pre: true ]] { return -1; } + +void dummy0() +{ + int F4::test0() [[ pre: true ]]; // { dg-error "qualified-id in declaration" } +} + +namespace ns0 +{ + typedef int value; + struct X + { + int test1(value); + typedef double value; + int test2(value); + }; + int X::test1(value); // { dg-error "no declaration matches" } + int X::test2(value); +} + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-redecl3.C b/gcc/testsuite/g++.dg/contracts/contracts-redecl3.C new file mode 100644 index 00000000000..fdfca3a65ff --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-redecl3.C @@ -0,0 +1,195 @@ +// basic test to ensure contracts generalized redecl works +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts -fcontract-continuation-mode=on" } +#include + +namespace defining +{ + int x = 10; + double y = 10.5; + + struct S + { + bool z; + }; + + struct T1 + { + void vfun(int m, double n); + int fun(int m, double n); + double fun2(int m, double n); + S funend(int m, double n); + }; + + void T1::vfun(int m, double n) + [[ pre: x < 0 ]] + [[ pre: m < 0 ]] + { + printf("vfun::x: %d\n", x); + } + + int T1::fun(int m, double n) + [[ pre: x < 0 ]] + { + printf("fun::x: %d\n", x); + return x; + } + + double T1::fun2(int m, double n) + [[ pre: x < 0 ]] + [[ pre: y < 0 ]] + [[ pre: m < 0 ]] + [[ pre: n < 0 ]] + { + printf("fun2::x: %d fun2::y: %f\n", x, y); + return y; + } +} + +namespace nondefining +{ + int x = 10; + double y = 10.5; + + struct S + { + bool z; + }; + + struct T1 + { + void vfun(int m, double n); + int fun(int m, double n); + double fun2(int m, double n); + S funend(int m, double n); + }; + + void T1::vfun(int m, double n) + [[ pre: x < 0 ]] + [[ pre: m < 0 ]]; + int T1::fun(int m, double n) + [[ pre: x < 0 ]]; + double T1::fun2(int m, double n) + [[ pre: x < 0 ]] + [[ pre: y < 0 ]] + [[ pre: m < 0 ]] + [[ pre: n < 0 ]]; + + void T1::vfun(int m, double n) + { + printf("vfun::x: %d\n", x); + } + + int T1::fun(int m, double n) + { + printf("fun::x: %d\n", x); + return x; + } + + double T1::fun2(int m, double n) + { + printf("fun2::x: %d fun2::y: %f\n", x, y); + return y; + } +} + +int main(int, char**) { + // defining redecl + { + int x = 11; + double y = 11.5; + + defining::T1 t1; + t1.vfun(x, y); + + int f = 13; + f = t1.fun(x, y); + printf("main::f: %d\n", f); + double d = 13.37; + d = t1.fun2(x, y); + printf("main::d: %f\n", d); + defining::S s = t1.funend(x, y); + printf("main::s.z: %d\n", s.z ? 1 : 0); + } + + // nondefining redecl + { + int x = 12; + double y = 12.5; + + nondefining::T1 t1; + t1.vfun(x, y); + + int f = 13; + f = t1.fun(x, y); + printf("main::f: %d\n", f); + double d = 13.37; + d = t1.fun2(x, y); + printf("main::d: %f\n", d); + nondefining::S s = t1.funend(x, y); + printf("main::s.z: %d\n", s.z ? 1 : 0); + } + return 0; +} + +namespace defining +{ + S T1::funend(int m, double n) + [[ pre: x < 0 ]] + [[ pre: m < 0 ]] + { + printf("funend::x: %d\n", x); + S s; + s.z = true; + return s; + } +} + +namespace nondefining +{ + S T1::funend(int m, double n) + [[ pre: x < 0 ]] + [[ pre: m < 0 ]]; + + S T1::funend(int m, double n) + { + printf("funend::x: %d\n", x); + S s; + s.z = true; + return s; + } +} + +// { dg-output "default std::handle_contract_violation called: .*.C 25 defining::T1::vfun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 26 defining::T1::vfun .*(\n|\r\n|\r)*" } +// { dg-output "vfun::x: 10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 32 defining::T1::fun .*(\n|\r\n|\r)*" } +// { dg-output "fun::x: 10(\n|\r\n|\r)*" } +// { dg-output "main::f: 10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 39 defining::T1::fun2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 40 defining::T1::fun2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 41 defining::T1::fun2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 42 defining::T1::fun2 .*(\n|\r\n|\r)*" } +// { dg-output "fun2::x: 10 fun2::y: 10.500000(\n|\r\n|\r)*" } +// { dg-output "main::d: 10.500000(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 138 defining::T1::funend .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 139 defining::T1::funend .*(\n|\r\n|\r)*" } +// { dg-output "funend::x: 10(\n|\r\n|\r)*" } +// { dg-output "main::s.z: 1(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 68 nondefining::T1::vfun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 69 nondefining::T1::vfun .*(\n|\r\n|\r)*" } +// { dg-output "vfun::x: 10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 71 nondefining::T1::fun .*(\n|\r\n|\r)*" } +// { dg-output "fun::x: 10(\n|\r\n|\r)*" } +// { dg-output "main::f: 10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 73 nondefining::T1::fun2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 74 nondefining::T1::fun2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 75 nondefining::T1::fun2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 76 nondefining::T1::fun2 .*(\n|\r\n|\r)*" } +// { dg-output "fun2::x: 10 fun2::y: 10.500000(\n|\r\n|\r)*" } +// { dg-output "main::d: 10.500000(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 151 nondefining::T1::funend .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 152 nondefining::T1::funend .*(\n|\r\n|\r)*" } +// { dg-output "funend::x: 10(\n|\r\n|\r)*" } +// { dg-output "main::s.z: 1(\n|\r\n|\r)*" } + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-redecl4.C b/gcc/testsuite/g++.dg/contracts/contracts-redecl4.C new file mode 100644 index 00000000000..c1e234226b6 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-redecl4.C @@ -0,0 +1,56 @@ +// test that free functions can be redeclared with contracts without affecting +// normal default parm handling +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts -fcontract-continuation-mode=on" } +#include + +int f(int a, int, int c = 10); +int f(int a, int b = 11, int); +int f(int a, int b, int c) + [[ pre: a < 0 ]] + [[ pre: b < 0 ]] + [[ pre: c < 0 ]]; + +int f(int, int, int); + +int f(int a, int b, int c) +{ + printf("f: a: %d, b: %d, c: %d\n", a, b, c); + return a * b - c; +} + +int f(int a = 12, int, int); + +int main(int, char **) +{ + f(1,1,1); + printf("=====\n"); + f(1,1); + printf("=====\n"); + f(1); + printf("=====\n"); + f(); + printf("=====\n"); +} + +// { dg-output "default std::handle_contract_violation called: .*.C 10 f .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 11 f .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 12 f .*(\n|\r\n|\r)*" } +// { dg-output "f: a: 1, b: 1, c: 1(\n|\r\n|\r)*" } +// { dg-output "=====(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 10 f .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 11 f .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 12 f .*(\n|\r\n|\r)*" } +// { dg-output "f: a: 1, b: 1, c: 10(\n|\r\n|\r)*" } +// { dg-output "=====(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 10 f .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 11 f .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 12 f .*(\n|\r\n|\r)*" } +// { dg-output "f: a: 1, b: 11, c: 10(\n|\r\n|\r)*" } +// { dg-output "=====(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 10 f .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 11 f .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 12 f .*(\n|\r\n|\r)*" } +// { dg-output "f: a: 12, b: 11, c: 10(\n|\r\n|\r)*" } +// { dg-output "=====(\n|\r\n|\r)*" } + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-redecl5.C b/gcc/testsuite/g++.dg/contracts/contracts-redecl5.C new file mode 100644 index 00000000000..ea4835f1899 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-redecl5.C @@ -0,0 +1,101 @@ +// generic error tests for generalized contract redecls +// we also test for the warning diagnostic for strict redecl +// { dg-do compile } +// { dg-options "-std=c++2a -fcontracts -fcontract-strict-declarations=on" } + +// allowed to repeat contracts or omit them +int g0(int a) [[ pre: a > 0 ]]; +int g0(int a) [[ pre: a > 0 ]]; + +int g1(int a) [[ pre: a > 0 ]]; +int g1(int a); + +// allowed to add from none if generalized redecl is on (by default) +int g2(int a); +int g2(int a) [[ pre: a > 0 ]]; // { dg-warning "adds contracts" } +int g2(int a) [[ pre: a > 0 ]]; // { dg-bogus "adds contracts" } + +// can add to non-virtual methods +struct G0 +{ + int f(int a); +}; + +int G0::f(int a) [[ pre: a > 0 ]] // { dg-warning "adds contracts" } +{ + return -a; +} + +struct G1 +{ + int f(int a); +}; + +int G1::f(int a) [[ pre: a > 0 ]]; // { dg-warning "adds contracts" } +// { dg-warning "outside of class is not definition" "" { target *-*-* } .-1 } + +int G1::f(int a); +// { dg-warning "outside of class is not definition" "" { target *-*-* } .-1 } + +int G1::f(int a) [[ pre: a > 0 ]]; +// { dg-warning "outside of class is not definition" "" { target *-*-* } .-1 } + +int G1::f(int a) +{ + return -a; +} + +// allowed to redeclare even without contracts +struct G2 +{ + int f(int a); +}; + +int G2::f(int a); // { dg-warning "outside of class is not definition" } + + +int f0(int a) [[ pre: a > 0 ]]; +int f0(int a) [[ pre: a > 0 ]] [[ pre: a > 10 ]]; // { dg-error "different number of contracts" } + +int f1(int a) [[ pre: a > 0 ]]; +int f1(int a) [[ pre: a < 0 ]]; // { dg-error "mismatched contract" } + +int f2(int a) { return a; } +int f2(int a) [[ pre: a < 0 ]]; // { dg-error "cannot add contracts after definition" } + +struct Base +{ + virtual int f(int a) [[ pre: a > 0 ]]; +}; + +struct Child : Base +{ + int f(int a) [[ pre: a < 0 ]]; // { dg-error "mismatched contract" } +}; + +// the initial decl of a guarded member must appear inside the class +struct F2 +{ + int f(int a); +}; + +int F2::g(int a) [[ pre: a > 0 ]]; // { dg-error "no declaration matches" } +// FIXME if we move F2 down then a different error makes F2 undeclared + +struct F0 +{ + virtual int f(int a); +}; + +int F0::f(int a); // { dg-error "declaration.*is not definition" } + +struct F1 +{ + virtual int f(int a); +}; + +int F1::f(int a) [[ pre: a > 0 ]] // { dg-error "cannot add" } +{ + return -a; +} + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-redecl6.C b/gcc/testsuite/g++.dg/contracts/contracts-redecl6.C new file mode 100644 index 00000000000..e79a5aa38ef --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-redecl6.C @@ -0,0 +1,195 @@ +// basic test to ensure contracts generalized redecl works +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts -fcontract-continuation-mode=on" } +#include + +namespace defining +{ + int x = 10; + double y = 10.5; + + struct S + { + bool z; + }; + + struct T1 + { + void vfun(int m, double n) const; + int fun(int m, double n) volatile; + double fun2(int m, double n) const volatile; + static S funend(int m, double n); + }; + + void T1::vfun(int m, double n) const + [[ pre: x < 0 ]] + [[ pre: m < 0 ]] + { + printf("vfun::x: %d\n", x); + } + + int T1::fun(int m, double n) volatile + [[ pre: x < 0 ]] + { + printf("fun::x: %d\n", x); + return x; + } + + double T1::fun2(int m, double n) const volatile + [[ pre: x < 0 ]] + [[ pre: y < 0 ]] + [[ pre: m < 0 ]] + [[ pre: n < 0 ]] + { + printf("fun2::x: %d fun2::y: %f\n", x, y); + return y; + } +} + +namespace nondefining +{ + int x = 10; + double y = 10.5; + + struct S + { + bool z; + }; + + struct T1 + { + void vfun(int m, double n) const; + int fun(int m, double n) volatile; + double fun2(int m, double n) const volatile; + static S funend(int m, double n); + }; + + void T1::vfun(int m, double n) const + [[ pre: x < 0 ]] + [[ pre: m < 0 ]]; + int T1::fun(int m, double n) volatile + [[ pre: x < 0 ]]; + double T1::fun2(int m, double n) const volatile + [[ pre: x < 0 ]] + [[ pre: y < 0 ]] + [[ pre: m < 0 ]] + [[ pre: n < 0 ]]; + + void T1::vfun(int m, double n) const + { + printf("vfun::x: %d\n", x); + } + + int T1::fun(int m, double n) volatile + { + printf("fun::x: %d\n", x); + return x; + } + + double T1::fun2(int m, double n) const volatile + { + printf("fun2::x: %d fun2::y: %f\n", x, y); + return y; + } +} + +int main(int, char**) { + // defining redecl + { + int x = 11; + double y = 11.5; + + defining::T1 t1; + t1.vfun(x, y); + + int f = 13; + f = t1.fun(x, y); + printf("main::f: %d\n", f); + double d = 13.37; + d = t1.fun2(x, y); + printf("main::d: %f\n", d); + defining::S s = defining::T1::funend(x, y); + printf("main::s.z: %d\n", s.z ? 1 : 0); + } + + // nondefining redecl + { + int x = 12; + double y = 12.5; + + nondefining::T1 t1; + t1.vfun(x, y); + + int f = 13; + f = t1.fun(x, y); + printf("main::f: %d\n", f); + double d = 13.37; + d = t1.fun2(x, y); + printf("main::d: %f\n", d); + nondefining::S s = nondefining::T1::funend(x, y); + printf("main::s.z: %d\n", s.z ? 1 : 0); + } + return 0; +} + +namespace defining +{ + S T1::funend(int m, double n) + [[ pre: x < 0 ]] + [[ pre: m < 0 ]] + { + printf("funend::x: %d\n", x); + S s; + s.z = true; + return s; + } +} + +namespace nondefining +{ + S T1::funend(int m, double n) + [[ pre: x < 0 ]] + [[ pre: m < 0 ]]; + + S T1::funend(int m, double n) + { + printf("funend::x: %d\n", x); + S s; + s.z = true; + return s; + } +} + +// { dg-output "default std::handle_contract_violation called: .*.C 25 defining::T1::vfun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 26 defining::T1::vfun .*(\n|\r\n|\r)*" } +// { dg-output "vfun::x: 10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 32 defining::T1::fun .*(\n|\r\n|\r)*" } +// { dg-output "fun::x: 10(\n|\r\n|\r)*" } +// { dg-output "main::f: 10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 39 defining::T1::fun2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 40 defining::T1::fun2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 41 defining::T1::fun2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 42 defining::T1::fun2 .*(\n|\r\n|\r)*" } +// { dg-output "fun2::x: 10 fun2::y: 10.500000(\n|\r\n|\r)*" } +// { dg-output "main::d: 10.500000(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 138 defining::T1::funend .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 139 defining::T1::funend .*(\n|\r\n|\r)*" } +// { dg-output "funend::x: 10(\n|\r\n|\r)*" } +// { dg-output "main::s.z: 1(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 68 nondefining::T1::vfun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 69 nondefining::T1::vfun .*(\n|\r\n|\r)*" } +// { dg-output "vfun::x: 10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 71 nondefining::T1::fun .*(\n|\r\n|\r)*" } +// { dg-output "fun::x: 10(\n|\r\n|\r)*" } +// { dg-output "main::f: 10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 73 nondefining::T1::fun2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 74 nondefining::T1::fun2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 75 nondefining::T1::fun2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 76 nondefining::T1::fun2 .*(\n|\r\n|\r)*" } +// { dg-output "fun2::x: 10 fun2::y: 10.500000(\n|\r\n|\r)*" } +// { dg-output "main::d: 10.500000(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 151 nondefining::T1::funend .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 152 nondefining::T1::funend .*(\n|\r\n|\r)*" } +// { dg-output "funend::x: 10(\n|\r\n|\r)*" } +// { dg-output "main::s.z: 1(\n|\r\n|\r)*" } + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-redecl7.C b/gcc/testsuite/g++.dg/contracts/contracts-redecl7.C new file mode 100644 index 00000000000..e3a57eea632 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-redecl7.C @@ -0,0 +1,95 @@ +// test that contracts can be added during (defining) friend declarations +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts -fcontract-continuation-mode=on" } +#include +struct T; + +struct S +{ + int now(int a, T *t) [[ pre: a > 0 ]] [[ pre: x < 0 ]]; + + + int x{-1}; +}; + +int now(int a, T *t) [[ pre: a > 0 ]]; +int later(int a, T *t); +int both(int a, T *t) [[ pre: a > 0 ]]; + +struct T +{ + friend int now(int a, T *t); + friend int later(int a, T *t) [[ pre: a > 0 ]] [[ pre: t->pri > 0 ]] + { + printf("later: a: %d, t->pri: %d\n", a, t->pri); + return -a * t->pri; + } + friend int both(int a, T *t) [[ pre: a > 0 ]] + { + printf("both: a: %d, t->pri: %d\n", a, t->pri); + return -a * t->pri; + } + + + friend int S::now(int a, T *t); + + friend int hidden(int a, T *t) [[ pre: a > 0 ]] [[ pre: t->pri > 0 ]] + { + printf("hidden: a: %d, t->pri: %d\n", a, t->pri); + return -a * t->pri; + } + friend int hidden2(int a, T *t) [[ pre: a > 0 ]] [[ pre: t->pri > 0 ]] + { + printf("hidden2: a: %d, t->pri: %d\n", a, t->pri); + return -a * t->pri; + } + + int x{1}; + private: + int pri{-10}; +}; + +int hidden2(int a, T *t) [[ pre: a > 0 ]] [[ pre: t->pri > 0 ]]; + +int S::now(int a, T *t) +{ + printf("S::now: a: %d, t->pri: %d\n", a, t->pri); + return -a * t->pri; +} + +int now(int a, T *t) +{ + printf("now: a: %d, t->pri: %d\n", a, t->pri); + return -a * t->pri; +} + +int main(int, char**) +{ + T t; + S s; + s.now(-10, &t); + + now(-20, &t); + later(-21, &t); + both(-22, &t); + hidden(-23, &t); + hidden2(-24, &t); + return 0; +} + +// { dg-output "default std::handle_contract_violation called: .*.C 9 S::now .*(\n|\r\n|\r)*" } +// { dg-output "S::now: a: -10, t->pri: -10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 15 now .*(\n|\r\n|\r)*" } +// { dg-output "now: a: -20, t->pri: -10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 22 later .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 22 later .*(\n|\r\n|\r)*" } +// { dg-output "later: a: -21, t->pri: -10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 27 both .*(\n|\r\n|\r)*" } +// { dg-output "both: a: -22, t->pri: -10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 36 hidden .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 36 hidden .*(\n|\r\n|\r)*" } +// { dg-output "hidden: a: -23, t->pri: -10(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 41 hidden2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 41 hidden2 .*(\n|\r\n|\r)*" } +// { dg-output "hidden2: a: -24, t->pri: -10(\n|\r\n|\r)*" } + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-redecl8.C b/gcc/testsuite/g++.dg/contracts/contracts-redecl8.C new file mode 100644 index 00000000000..933adce79f9 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-redecl8.C @@ -0,0 +1,64 @@ +// test that contracts are matched on friend decls when the type is complete +// { dg-do compile } +// { dg-options "-std=c++2a -fcontracts" } + +struct T; + +int both(int x, T *t) [[ pre: x > 0 ]] { return 0; } +int both2(int x, T *t) [[ pre: x > 0 ]]; + +template +int fn(int x, Z *z) [[ pre: x > 0 ]]; + +template +int fn2(int x, Z *z); + +template +int fn3(int x, Z *z) [[ pre: x > 0 ]]; + +template<> +int fn3(int x, T *z) [[ pre: x > 1 ]]; + +struct T +{ + friend int both2(int x, T *t) [[ pre: x > 1 ]] // { dg-error "mismatched" } + { + return 0; + } + + friend int hidden(int x, T *t) + [[ pre: x > 1 ]] [[ pre: t->pri > 0 ]] + { + return x; + } + + /* cannot define friend spec, so we never get to matching contracts + friend int fn(int x, T *t) + [[ pre: t->pri > 0 ]] { return 0; } // error defining explicit spec friend + */ + + // bad, general contracts must match general + template + friend int fn(int x, Z *z) + [[ pre: z->pri > 1 ]] { return 0; } // { dg-error "mismatched" } + + // fine, can add contracts + template + friend int fn2(int x, Z *z) + [[ pre: z->pri > 1 ]] { return 0; } // { dg-bogus "mismatched" } + + /* cannot declare without definition, so dup friend can't occur: + friend int dup(int x, T *t) + [[ pre: t->pri > 0 ]]; // error non-defining friend with contracts + friend int dup(int x, T *t) + [[ pre: t->pri > 1 ]]; // error non-defining friend with contracts + */ + + int x{1}; + private: + int pri{-10}; +}; + +int hidden(int x, T *t) + [[ pre: x > 0 ]] [[ pre: t->pri > 1 ]]; // { dg-error "mismatched" } + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-tmpl-attr1.C b/gcc/testsuite/g++.dg/contracts/contracts-tmpl-attr1.C new file mode 100644 index 00000000000..c9e7b9cec8b --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-tmpl-attr1.C @@ -0,0 +1,19 @@ +// { dg-do compile } +// { dg-options "-std=c++2a -fcontracts" } + +template +[[z]] +[[nodiscard]] +T fun(T n) + [[ pre: n > 0 ]] + [[ post r: r > 0 ]] // { dg-warning ".z. attribute.*ignored" } +{ + return n; +} + +int main(int, char**) { + fun(-5); // { dg-warning "ignoring return value" } + fun(-5.3); // { dg-warning "ignoring return value" } + return 0; +} + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-tmpl-spec1.C b/gcc/testsuite/g++.dg/contracts/contracts-tmpl-spec1.C new file mode 100644 index 00000000000..57ba7653543 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-tmpl-spec1.C @@ -0,0 +1,121 @@ +// basic test to ensure pre contracts work for free template specializations +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts -fcontract-continuation-mode=on" } +#include + +template +int body(int a) + [[ pre: a > 0 ]] +{ + T t = a * 2.5; + return t; +} + +template<> +int body(int a) + [[ pre: a > 0 ]] +{ + double t = a * 3.3; + return t; +} + +template +int none(int a) + [[ pre: a > 0 ]] +{ + return -a; +} + +template<> +int none(int a) + [[ pre: a > 0 ]] +{ + return a - 100; +} + +template +int arg0(T t) + [[ pre: t > 0 ]] +{ + return -t - 10; +} + +template<> +int arg0(double t) + [[ pre: t > 0 ]] +{ + return -t + 10; +} + +template +int arg1(int a, T t) + [[ pre: a > 0 ]] + [[ pre: t > 0 ]] +{ + return -t * a; +} + +template<> +int arg1(int a, double t) + [[ pre: a > 0 ]] + [[ pre: t > 0 ]] +{ + return -t * a + 17; +} + +template +T ret(int a) + [[ pre: a > 0 ]] +{ + return -a; +} + +template<> +double ret(int a) + [[ pre: a > 0 ]] +{ + return -a * 3.3; +} + + +int main(int, char**) +{ + printf("%d\n", body(-1)); + printf("%d\n", body(-1)); + printf("%d\n", none(-1)); + printf("%d\n", none(-1)); + printf("%d\n", arg0(-1)); + printf("%d\n", arg0(-1.0)); + printf("%d\n", arg1(-3, -1)); + printf("%d\n", arg1(-3, -1.0)); + printf("%d\n", (int)ret(-1)); + printf("%d\n", (int)ret(-1)); + printf("%f\n", ret(-1)); + return 0; +} + +// { dg-output "default std::handle_contract_violation called: .*.C 8 body .*(\n|\r\n|\r)*" } +// { dg-output "-2(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 16 body .*(\n|\r\n|\r)*" } +// { dg-output "-3(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 24 none .*(\n|\r\n|\r)*" } +// { dg-output "1(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 31 none .*(\n|\r\n|\r)*" } +// { dg-output "-101(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 38 arg0 .*(\n|\r\n|\r)*" } +// { dg-output "-9(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 45 arg0 .*(\n|\r\n|\r)*" } +// { dg-output "11(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 52 arg1 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 53 arg1 .*(\n|\r\n|\r)*" } +// { dg-output "-3(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 60 arg1 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 61 arg1 .*(\n|\r\n|\r)*" } +// { dg-output "14(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 68 ret .*(\n|\r\n|\r)*" } +// { dg-output "1(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 75 ret .*(\n|\r\n|\r)*" } +// { dg-output "3(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 75 ret .*(\n|\r\n|\r)*" } +// { dg-output "3.300000(\n|\r\n|\r)*" } + diff --git a/gcc/testsuite/g++.dg/contracts/contracts-tmpl-spec2.C b/gcc/testsuite/g++.dg/contracts/contracts-tmpl-spec2.C new file mode 100644 index 00000000000..25982dfc826 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts-tmpl-spec2.C @@ -0,0 +1,395 @@ +// basic test to ensure contracts work for class and member specializations +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts -fcontract-continuation-mode=on" } +#include + +// template specializations can have differing contracts +template +int body(int a) + [[ pre: a > 0 ]] +{ + T t = a * 2.5; + return t; +} + +template<> +int body(int a) + [[ pre: a > 1 ]] +{ + double t = a * 3.3; + return t; +} + +template +int none(int a) + [[ pre: a > 0 ]] +{ + return -a; +} + +template<> +int none(int a) + [[ pre: a > 1 ]] +{ + return a - 100; +} + +template +int arg0(T t) + [[ pre: t > 0 ]] +{ + return -t - 10; +} + +template<> +int arg0(double t) + [[ pre: t > 1 ]] +{ + return -t + 10; +} + +template +int arg1(int a, T t) + [[ pre: a > 0 ]] + [[ pre: t > 0 ]] +{ + return -t * a; +} + +template<> +int arg1(int a, double t) + [[ pre: a > 1 ]] + [[ pre: t > 1 ]] +{ + return -t * a + 17; +} + +template +T ret(int a) + [[ pre: a > 0 ]] +{ + return -a; +} + +template<> +double ret(int a) + [[ pre: a > 1 ]] +{ + return -a * 3.3; +} + +// template specializations can have no contracts +template +int g1(T t) [[ pre: t > 0 ]] +{ + return (int)t; +} + +template<> +int g1(double t) +{ + return (int)t; +} + +// template specializations can have no contracts in the first decl but add +// them later +template +int g2(T t) [[ pre: t > 0 ]] +{ + return (int)t; +} + +template<> +int g2(double t); + +template<> +int g2(double t) + [[ pre: t < 0 ]] +{ + return (int)t; +} + +template<> +int g2(char t) + [[ pre: t < 'c' ]] +{ + return (int)t; +} + +// contracts can be different on the general template, partial and full specs +template +struct G3 +{ + void f(T t, S s) + [[ pre: t > 0 ]] + [[ pre: s > 0 ]] + { + printf ("G3 general T S\n"); + } +}; + +template +struct G3 +{ + void f(int t, S s); +}; + +template +void G3::f(int t, S s) + [[ pre: t > 1 ]] + [[ pre: s > 1 ]] +{ + printf ("G3 partial int S\n"); +} + +template<> +void G3::f(int t, double s) + [[ pre: t > 2 ]] + [[ pre: s > 2 ]] +{ + printf ("G3 full int double\n"); +} + +struct C +{ + bool operator>(int rhs) { return false; } +}; + +// deletes contracts +template<> +void G3::f(int t, C s); + +template<> +void G3::f(int t, C s) +{ + printf ("G3 full int C\n"); +}; + +// specialized ctors +template +struct G4 +{ + G4(T t, S s) + [[ pre: t > 0 ]] + [[ pre: s > 0 ]] + [[ post: x > 0 ]] + { + printf ("G4 general T S\n"); + return; + } + int x{-1}; +}; + +template +struct G4 +{ + G4(char t, S s) + [[ pre: t > 'c' ]] + [[ pre: s > 3 ]] + [[ post: x2 > 3 ]] + { + printf ("G4 partial char S\n"); + return; + } + int x2{-1}; +}; + +template<> +G4::G4(double, double) +{ + printf ("G4 full double double\n"); + return; +} + +template<> +G4::G4(double a, char b) + [[ pre: a > 0 ]] + [[ pre: b > 'b' ]] + [[ post: x > 1 ]] +{ + printf ("G4 full double char\n"); + return; +} + +// crossover of template classes and template members ok +template +struct G5 +{ + template + void f(T t, S s, P r) + [[ pre: t > 0 ]] + [[ pre: s > 0 ]] + [[ pre: r > 0 ]] + { + printf ("G5 gen T S, f gen R\n"); + } +}; + +template +struct G5 +{ + template + void f(char x, S y, R z) + [[ pre: x > 'z' ]] + [[ pre: y > 1 ]] + [[ pre: z > 1 ]] + { + printf ("G5 partial char S, f gen R\n"); + } +}; + +template<> +template +void G5::f(double a, double b, Q c) + [[ pre: a > 2 ]] + [[ pre: b > 2 ]] + [[ pre: c > 2 ]] +{ + printf ("G5 full double double, f gen R\n"); +} + +int main(int, char**) +{ + printf("%d\n", body(-1)); + printf("%d\n", body(-1)); + printf("%d\n", none(-1)); + printf("%d\n", none(-1)); + printf("%d\n", arg0(-1)); + printf("%d\n", arg0(-1.0)); + printf("%d\n", arg1(-3, -1)); + printf("%d\n", arg1(-3, -1.0)); + printf("%d\n", (int)ret(-1)); + printf("%d\n", (int)ret(-1)); + printf("%f\n", ret(-1)); + + printf("%d\n", g1(-1)); + printf("%d\n", g1(-1.0)); + + printf("%d\n", g2(-1)); + printf("%d\n", g2(1.0)); + printf("%d\n", g2('d')); + + G3 g3_gen; + G3 g3_partial; + G3 g3_full; + g3_gen.f(-1.0, -1.0); // general + g3_partial.f(-2, -2); // partial spec + g3_full.f(-3, -3.0); // full spec + + G3 g3_gen2; + G3 g3_partial2; + g3_gen2.f((char)-1, (char)-1); + g3_partial2.f(-1, (char)-1); + + G3 g3_full2; + g3_full2.f(5, C{}); + g3_full2.f(-5, C{}); + + G4 g4_gen{-1, -1}; + G4 g4_full1{-1.0, -1.0}; + G4 g4_full2{-1.0, (char)'b'}; + G4 g4_partial{(char)'c', -5}; + + G5 g5_gen; + g5_gen.f(-1, -1, -2); + g5_gen.f(-1, -1, -2.0); + + G5 g5_part; + g5_part.f('a', -1, -2); + g5_part.f('a', -1, -2.1); + + G5 g5_full; + g5_full.f(-1.0, -1.0, -2); + g5_full.f(-1.0, -1.0, -2.1); + return 0; +} + + +// { dg-output "default std::handle_contract_violation called: .*.C 9 body .*(\n|\r\n|\r)*" } +// { dg-output "-2(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 17 body .*(\n|\r\n|\r)*" } +// { dg-output "-3(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 25 none .*(\n|\r\n|\r)*" } +// { dg-output "1(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 32 none .*(\n|\r\n|\r)*" } +// { dg-output "-101(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 39 arg0 .*(\n|\r\n|\r)*" } +// { dg-output "-9(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 46 arg0 .*(\n|\r\n|\r)*" } +// { dg-output "11(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 53 arg1 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 54 arg1 .*(\n|\r\n|\r)*" } +// { dg-output "-3(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 61 arg1 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 62 arg1 .*(\n|\r\n|\r)*" } +// { dg-output "14(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 69 ret .*(\n|\r\n|\r)*" } +// { dg-output "1(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 76 ret .*(\n|\r\n|\r)*" } +// { dg-output "3(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 76 ret .*(\n|\r\n|\r)*" } +// { dg-output "3.300000(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 83 g1 .*(\n|\r\n|\r)*" } +// { dg-output "-1(\n|\r\n|\r)*" } +// { dg-output "-1(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 97 g2 .*(\n|\r\n|\r)*" } +// { dg-output "-1(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 107 g2 .*(\n|\r\n|\r)*" } +// { dg-output "1(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 114 g2 .*(\n|\r\n|\r)*" } +// { dg-output "100(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 124 G3 + +template +struct G5 +{ + template + void f(T t, S s, R r) + [[ pre: t > 0 ]] [[ pre: s > 0 ]] [[ pre: r > 0 ]] + { + printf ("G5 gen T S, f gen R\n"); + } +}; + +// specializations can remove contracts +template<> +template +void G5::f(double a, double b, R c) +{ + printf ("G5 full double double, f gen R\n"); +} + +int main(int, char**) { + G5 g5_full; + g5_full.f(-1.0, -1.0, -2); + g5_full.f(-1.0, -1.0, -2.1); + + G5 g5_gen; + g5_gen.f(-1, -1.0, -2); + g5_gen.f(-1, -1.0, -2.1); + return 0; +} + +// { dg-output "G5 full double double, f gen R(\n|\r\n|\r)*" } +// { dg-output "G5 full double double, f gen R(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 10 G5 0 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 10 G5 0 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 10 G5 0 .*(\n|\r\n|\r)*" } +// { dg-output "G5 gen T S, f gen R(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 10 G5 0 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 10 G5 0 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 10 G5 0 .*(\n|\r\n|\r)*" } +// { dg-output "G5 gen T S, f gen R(\n|\r\n|\r)*" } + diff --git a/gcc/testsuite/g++.dg/contracts/contracts1.C b/gcc/testsuite/g++.dg/contracts/contracts1.C new file mode 100644 index 00000000000..6655e016221 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts1.C @@ -0,0 +1,49 @@ +// generic assert contract parsing checks +// check omitted, 'default', 'audit', and 'axiom' contract levels parse +// check that all concrete semantics parse +// check omitted, '%default' contract roles parse +// ensure that an invalid contract level 'invalid' errors +// ensure that a predicate referencing an undefined variable errors +// ensure that a missing colon after contract level errors +// ensure that an invalid contract role 'invalid' errors +// ensure that a missing colon after contract role errors +// { dg-do compile } +// { dg-options "-std=c++2a -fcontracts" } + +static_assert (__cpp_contracts >= 201906); +static_assert (__cpp_contracts_literal_semantics >= 201906); +static_assert (__cpp_contracts_roles >= 201906); + +int main() +{ + int x; + + [[assert: x >= 0]]; + [[assert default: x < 0]]; + [[assert audit: x == 0]]; + [[assert axiom: x == 1]]; + + [[assert: x > 0 ? true : false]]; + [[assert: x < 0 ? true : false]]; + + [[assert: x = 0]]; // { dg-error "expected .]. before .=. token" } + + [[assert ignore: x >= 0]]; + [[assert assume: x >= 0]]; + [[assert check_never_continue: x >= 0]]; + [[assert check_maybe_continue: x >= 0]]; + + [[assert %default: x >= 0]]; + [[assert default %default: x < 0]]; + [[assert audit %default: x == 0]]; + [[assert axiom %default: x == 1]]; + + [[assert check_always_continue: x >= 0]]; // { dg-error "expected contract level" } + [[assert invalid: x == 0]]; // { dg-error "expected contract level" } + [[assert: y == 0]]; // { dg-error ".y. was not declared in this scope" } + [[assert default x == 0]]; // { dg-error "expected .:. before .x." } + [[assert %default x >= 0]]; // { dg-error "expected .:. before .x." } + + [[assert %invalid: x >= 0]]; // TODO: optional warning? + return 0; +} diff --git a/gcc/testsuite/g++.dg/contracts/contracts10.C b/gcc/testsuite/g++.dg/contracts/contracts10.C new file mode 100644 index 00000000000..ce0723c3db4 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts10.C @@ -0,0 +1,73 @@ +// general checks to ensure that contract violations are generated during +// runtime when appropriate +// each check also validates the expected file name, line number, function, +// predicate, and contract level are included in the violation_info object +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts -fcontract-build-level=audit -fcontract-continuation-mode=on" } + +namespace tns +{ + int fun() + { + int x = 1; + [[ assert: x < 0 ]]; + return 0; + } + int fun2(); + + struct TestType + { + static int fun(); + static int fun2() + { + int x = 1; + [[ assert: x < 0 ]]; + return 0; + } + }; +} + +int tns::fun2() +{ + int x = 1; + [[ assert: x < 0 ]]; + return 0; +} + +int tns::TestType::fun() +{ + int x = 1; + [[ assert: x < 0 ]]; + return 0; +} + +int main() +{ + int x = 100; + [[assert: x < 0]]; + [[assert default: x < 1]]; + [[assert audit: x < 2]]; +// contract_violation.line_number() may eventually come from +// std::source_location which *is* affected by the #line macro; our current +// implementation conforms to this so we've included it as a check +#line 100 + [[assert: x < 3]]; + [[assert axiom: x < 4]]; + + tns::fun(); + tns::fun2(); + + tns::TestType::fun(); + tns::TestType::fun2(); + return 0; +} + +// { dg-output "default std::handle_contract_violation called: .*.C 47 main .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 48 main .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 49 main .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 100 main .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 13 tns::fun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 33 tns::fun2 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 40 tns::TestType::fun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 24 tns::TestType::fun2 .*(\n|\r\n|\r)*" } + diff --git a/gcc/testsuite/g++.dg/contracts/contracts11.C b/gcc/testsuite/g++.dg/contracts/contracts11.C new file mode 100644 index 00000000000..b41bc535e5d --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts11.C @@ -0,0 +1,103 @@ +// ensure that assert contract predicates that are not convertible to bool +// generate an error +// ensure the same for instatiated template functions +// ensure the same for non-instatiated template functions when the predicate +// is not dependent on the template parameters +// ensure template parameter dependent, potentially non-boolean, contract +// predicates do not generate an error if never instatiated +// { dg-do compile } +// { dg-options "-std=c++2a -fcontracts" } + +void fun() +{ + return; +} + +template +void fun2(T a) +{ + [[assert: fun()]]; // { dg-error "could not convert|in argument" } +} + +template +void fun3(T a) +{ + [[assert: fun()]]; // { dg-error "could not convert|in argument" } +} + +template +void fun4(T a) +{ + [[assert: a.fun()]]; +} + +struct test +{ + void fun() { } + void fun2() { } +}; + +template +void fun5(T a) +{ + [[assert: a.fun2()]]; // { dg-error "could not convert" } +} + +struct VoidFun +{ + void fun() { } +}; +struct BoolFun +{ + bool fun() { return true; } +}; + +template +void fun6(T a) +{ + [[ assert: a.fun() ]]; // { dg-error "could not convert" } +} + +template void fun6(VoidFun); + +template +void fun7(T a) +{ + [[ assert: a.fun() ]]; +} + +template void fun7(BoolFun); + +struct ImplicitBool +{ + operator bool() { return true; } +}; +struct ExplicitBool +{ + explicit operator bool() { return true; } +}; + +template +void fun8(T a) +{ + [[ assert: T() ]]; +} + +template void fun8(ImplicitBool); +template void fun8(ExplicitBool); + +void fun9() +{ + [[ assert: ImplicitBool() ]]; + [[ assert: ExplicitBool() ]]; +} + +int main() +{ + [[assert: fun()]]; // { dg-error "could not convert" } + fun2(1); + + test t; + fun5(t); + return 0; +} diff --git a/gcc/testsuite/g++.dg/contracts/contracts12.C b/gcc/testsuite/g++.dg/contracts/contracts12.C new file mode 100644 index 00000000000..f888d51296d --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts12.C @@ -0,0 +1,15 @@ +// ensure that constants for contract levels are inserted into the binary when +// used and omitted when the runtime check is not generated +// { dg-do compile } +// { dg-options "-std=c++2a -fcontracts -fcontract-build-level=default" } +// { dg-final { scan-assembler-not "audit" } } +// { dg-final { scan-assembler "default" } } + +int main() +{ + int x = 1; + [[assert: x < 0]]; + [[assert default: x < 0]]; + [[assert audit: x < 0]]; + return 0; +} diff --git a/gcc/testsuite/g++.dg/contracts/contracts13.C b/gcc/testsuite/g++.dg/contracts/contracts13.C new file mode 100644 index 00000000000..14ba0e96006 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts13.C @@ -0,0 +1,51 @@ +// ensure that passing asserts do not affect constexpr functions +// ensure that failing asserts generate an error in a constexpr function +// { dg-do compile } +// { dg-options "-std=c++2a -fcontracts" } + +constexpr int wfun(int a) { + [[assert: a > 0]]; + return a; +} + +constexpr int ffun(int a) { + [[assert: a > 0]]; + return a; +} + +template +constexpr int tfun(T a) { + [[assert: a > 0]]; + return a; +} + +template +constexpr int wtfun(T a) { + [[assert: a > 0]]; + return a; +} + +template +constexpr int ftfun(T a) { + [[assert: a > 0]]; + return a; +} + +constexpr int explicitfn(int a) { + [[assert ignore: a > 0]]; + [[assert check_never_continue: a > 0]]; + return a; +} + +int main(int, char **) { + constexpr int a = wfun(10); + constexpr int b = ffun(-10); // { dg-message "in .constexpr. expansion" } + // { dg-error "contract predicate" "" { target *-*-* } 12 } + constexpr int c = wtfun(10); + constexpr int d = ftfun(-10); // { dg-message "in .constexpr. expansion" } + // { dg-error "contract predicate" "" { target *-*-* } 30 } + constexpr int e = explicitfn(-10); // { dg-message "in .constexpr. expansion" } + // { dg-error "contract predicate" "" { target *-*-* } 36 } + return 0; +} + diff --git a/gcc/testsuite/g++.dg/contracts/contracts14.C b/gcc/testsuite/g++.dg/contracts/contracts14.C new file mode 100644 index 00000000000..55208dbc0a1 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts14.C @@ -0,0 +1,58 @@ +// ensure that exceptions thrown inside a custom contract violation handler +// are catchable up the call stack +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts -fcontract-continuation-mode=on" } +#include +#include + +void handle_contract_violation(const std::experimental::contract_violation &violation) { + std::cerr << "custom std::handle_contract_violation called:" + << " " << violation.line_number() + << " " << violation.file_name() + << std::endl; + throw -(int)violation.line_number(); +} + +int fun() { + int x = 0; + [[ assert: x < 0 ]]; + return 0; +} + +int fun3() { + fun(); + return 2; +} + +int main(int, char**) { + try { + int x = 0; + [[ assert: x < 0 ]]; + } catch(int &ex) { + std::cerr << "synth caught direct: " << ex << std::endl; + } + + try { + fun(); + } catch(int &ex) { + std::cerr << "synth caught indirect: " << ex << std::endl; + } + + try { + fun3(); + } catch(int &ex) { + std::cerr << "synth caught double indirect: " << ex << std::endl; + } + + std::cerr << "end main" << std::endl; + return 0; +} + +// { dg-output "custom std::handle_contract_violation called: 30 .*/contracts14.C(\n|\r\n|\r)*" } +// { dg-output "synth caught direct: -30(\n|\r\n|\r)*" } +// { dg-output "custom std::handle_contract_violation called: 18 .*/contracts14.C(\n|\r\n|\r)*" } +// { dg-output "synth caught indirect: -18(\n|\r\n|\r)*" } +// { dg-output "custom std::handle_contract_violation called: 18 .*/contracts14.C(\n|\r\n|\r)*" } +// { dg-output "synth caught double indirect: -18(\n|\r\n|\r)*" } +// { dg-output "end main" } + diff --git a/gcc/testsuite/g++.dg/contracts/contracts15.C b/gcc/testsuite/g++.dg/contracts/contracts15.C new file mode 100644 index 00000000000..d822f833916 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts15.C @@ -0,0 +1,56 @@ +// ensure that exceptions thrown inside a custom contract violation handler +// are not catchable up the call stack when failing in a noexcept function +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts -fcontract-continuation-mode=on" } +#include +#include + +void handle_contract_violation(const std::experimental::contract_violation &violation) { + std::cerr << "custom std::handle_contract_violation called:" + << " " << violation.line_number() + << " " << violation.file_name() + << std::endl; + throw -(int)violation.line_number(); +} + +int fun() noexcept { + int x = 0; + [[ assert: x < 0 ]]; + return 0; +} + +int fun3() { + fun(); + return 2; +} + +int main(int, char**) { + try { + int x = 0; + [[ assert: x < 0 ]]; + } catch(int &ex) { + std::cerr << "synth caught direct: " << ex << std::endl; + } + + try { + fun(); + } catch(int &ex) { + std::cerr << "synth caught indirect: " << ex << std::endl; + } + + try { + fun3(); + } catch(int &ex) { + std::cerr << "synth caught double indirect: " << ex << std::endl; + } + + std::cerr << "end main" << std::endl; + return 0; +} + +// { dg-output "custom std::handle_contract_violation called: 30 .*/contracts15.C(\n|\r\n|\r)*" } +// { dg-output "synth caught direct: -30(\n|\r\n|\r)*" } +// { dg-output "custom std::handle_contract_violation called: 18 .*/contracts15.C(\n|\r\n|\r)*" } +// { dg-output "terminate called after throwing an instance of .int.(\n|\r\n|\r)*" } +// { dg-shouldfail "throwing in noexcept" } + diff --git a/gcc/testsuite/g++.dg/contracts/contracts16.C b/gcc/testsuite/g++.dg/contracts/contracts16.C new file mode 100644 index 00000000000..1c7054507f2 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts16.C @@ -0,0 +1,34 @@ +// ensure that exceptions thrown inside a custom contract violation handler +// are not catchable up the call stack even when continue mode is off +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts" } +#include +#include + +void handle_contract_violation(const std::experimental::contract_violation &violation) { + std::cerr << "custom std::handle_contract_violation called:" + << " " << violation.line_number() + << " " << violation.file_name() + << std::endl; + throw -(int)violation.line_number(); +} + +int fun() { + int x = 0; + [[ assert: x < 0 ]]; + return 0; +} + +int main(int, char**) { + try { + fun(); + } catch(int &ex) { + std::cerr << "synth caught indirect: " << ex << std::endl; + } + + return 0; +} + +// { dg-output "custom std::handle_contract_violation called: 18 .*/contracts16.C(\n|\r\n|\r)*" } +// { dg-output "synth caught indirect: -18(\n|\r\n|\r)*" } + diff --git a/gcc/testsuite/g++.dg/contracts/contracts17.C b/gcc/testsuite/g++.dg/contracts/contracts17.C new file mode 100644 index 00000000000..d165bb05315 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts17.C @@ -0,0 +1,35 @@ +// ensure that exceptions thrown inside a custom contract violation handler +// are not catchable up the call stack when continue mode is off and the +// assert fails in a noexcept function +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts" } +#include +#include + +void handle_contract_violation(const std::experimental::contract_violation &violation) { + std::cerr << "custom std::handle_contract_violation called:" + << " " << violation.line_number() + << " " << violation.file_name() + << std::endl; + throw -violation.line_number(); +} + +int fun() noexcept { + int x = 0; + [[ assert: x < 0 ]]; + return 0; +} + +int main(int, char**) { + try { + fun(); + } catch(int &ex) { + std::cerr << "synth caught indirect: " << ex << std::endl; + } + + return 0; +} + +// { dg-output "custom std::handle_contract_violation called: 19 .*/contracts17.C(\n|\r\n|\r)*" } +// { dg-shouldfail "throwing in noexcept" } + diff --git a/gcc/testsuite/g++.dg/contracts/contracts18.C b/gcc/testsuite/g++.dg/contracts/contracts18.C new file mode 100644 index 00000000000..e8163ba4ab2 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts18.C @@ -0,0 +1,15 @@ +// check that a valid program using assertions compiles and runs +// ensure an axiom with a failing predicate doesn't prevent a successful run +// (axiom level contracts are never checked at runtime) +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts -fcontract-role=custom:never,ignore,ignore" } + +int main() +{ + int x = 1; + [[assert axiom: x < 0]]; + [[assert %custom: x > 0]]; + [[assert audit %custom: x < 0]]; + return 0; +} + diff --git a/gcc/testsuite/g++.dg/contracts/contracts19.C b/gcc/testsuite/g++.dg/contracts/contracts19.C new file mode 100644 index 00000000000..4a8b43a3186 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts19.C @@ -0,0 +1,19 @@ +// check that a valid program using assertions compiles and runs +// ensure an axiom with a failing predicate doesn't prevent a successful run +// (axiom level contracts are never checked at runtime) +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts -fcontract-role=custom:maybe,maybe,ignore" } + +int main() +{ + int x = 10; + [[assert axiom: x < 0]]; + [[assert %custom: x < 0]]; + [[assert audit %custom: x < 1]]; + [[assert axiom %custom: x < 1]]; + return 0; +} + +// { dg-output "default std::handle_contract_violation called: .*.C 11 main .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 12 main .*(\n|\r\n|\r)*" } + diff --git a/gcc/testsuite/g++.dg/contracts/contracts2.C b/gcc/testsuite/g++.dg/contracts/contracts2.C new file mode 100644 index 00000000000..9535e077d36 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts2.C @@ -0,0 +1,13 @@ +// check that a valid program using assertions compiles and runs +// ensure an axiom with a failing predicate doesn't prevent a successful run +// (axiom level contracts are never checked at runtime) +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts" } + +int main() +{ + int x = 1; + [[assert axiom: x < 0]]; + [[assert default: x > 0]]; + return 0; +} diff --git a/gcc/testsuite/g++.dg/contracts/contracts20.C b/gcc/testsuite/g++.dg/contracts/contracts20.C new file mode 100644 index 00000000000..adce5d70d40 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts20.C @@ -0,0 +1,11 @@ +// test that contract attributes cause errors pre-c++2a +// { dg-do compile { target c++17_only } } + +int fun(int a) + [[ pre: a > 0 ]] // { dg-error "contracts are only available with .-fcontracts." } + [[ post r: r < 0 ]] // { dg-error "contracts are only available with .-fcontracts." } +{ + [[ assert: a != 0 ]]; // { dg-error "contracts are only available with .-fcontracts." } + return -a; +} + diff --git a/gcc/testsuite/g++.dg/contracts/contracts22.C b/gcc/testsuite/g++.dg/contracts/contracts22.C new file mode 100644 index 00000000000..91e32b9d9b2 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts22.C @@ -0,0 +1,32 @@ +// ensure a default level assert with a failing predicate does not generate an +// error during runtime when the contracts mode is off +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts -fcontract-mode=off" } +// { dg-output "returning from main" } +#include + +int constexpr f() +{ + constexpr int x = 1; + [[assert default: x < 0]]; + return x; +} + +template int k() +{ + int x = 1; + [[assert default: x < 0]]; + return x; +} + + +int main() +{ + int x = 1; + [[assert default: x < 0]]; + constexpr int x2 = f(); + int x3 = k(); + + printf ("returning from main\n"); + return 0; +} diff --git a/gcc/testsuite/g++.dg/contracts/contracts24.C b/gcc/testsuite/g++.dg/contracts/contracts24.C new file mode 100644 index 00000000000..70a54f95a93 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts24.C @@ -0,0 +1,15 @@ +// check that a valid program using assertions compiles and runs +// ensure an axiom with a failing predicate doesn't prevent a successful run +// (axiom level contracts are never checked at runtime) +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts -fcontract-semantic=default:never -fcontract-semantic=audit:ignore -fcontract-semantic=axiom:ignore" } + +int main() +{ + int x = 1; + [[assert axiom: x < 0]]; + [[assert: x > 0]]; + [[assert audit: x < 0]]; + return 0; +} + diff --git a/gcc/testsuite/g++.dg/contracts/contracts25.C b/gcc/testsuite/g++.dg/contracts/contracts25.C new file mode 100644 index 00000000000..01217807bc1 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts25.C @@ -0,0 +1,57 @@ +// ensure that passing asserts do not affect constexpr functions +// ensure that failing asserts generate an error at runtime in constexpr funcs +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts -fcontract-continuation-mode=on" } + +constexpr int wfun(int a) { + [[assert: a > 0]]; + return a; +} + +constexpr int ffun(int a) { + [[assert: a > 0]]; + return a; +} + +template +constexpr int tfun(T a) { + [[assert: a > 0]]; + return a; +} + +template +constexpr int wtfun(T a) { + [[assert: a > 0]]; + return a; +} + +template +constexpr int ftfun(T a) { + [[assert: a > 0]]; + return a; +} + +constexpr int explicitfn(int a) { + [[assert ignore: a > 0]]; + [[assert check_maybe_continue: a > 0]]; + return a; +} + +int main(int, char **) { + int a = wfun(10); + int b = ffun(-10); + int c = wtfun(10); + int d = ftfun(-10); + + int e = explicitfn(-10); + + int z = ftfun(-10.0); + + return 0; +} + +// { dg-output "default std::handle_contract_violation called: .*.C 12 ffun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 30 ftfun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 36 explicitfn .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 30 ftfun .*(\n|\r\n|\r)*" } + diff --git a/gcc/testsuite/g++.dg/contracts/contracts3.C b/gcc/testsuite/g++.dg/contracts/contracts3.C new file mode 100644 index 00000000000..ecb9fdb1e65 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts3.C @@ -0,0 +1,13 @@ +// ensure a default level assert with a failing predicate generates an error +// during runtime when the contract build level is default +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts" } +// { dg-shouldfail "assert violation" } +// { dg-output "default std::handle_contract_violation called" } + +int main() +{ + int x = 1; + [[assert default: x < 0]]; + return 0; +} diff --git a/gcc/testsuite/g++.dg/contracts/contracts35.C b/gcc/testsuite/g++.dg/contracts/contracts35.C new file mode 100644 index 00000000000..ddd80025afc --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts35.C @@ -0,0 +1,47 @@ +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts -fcontract-continuation-mode=on" } +#include + +struct S +{ + template + S(T a) [[ pre: a > 0 ]] [[ pre: a > 10 ]]; +}; + +template +S::S(T a) +{ + printf ("S::S(T): %d\n", (int)a); +} + +struct S1 +{ + template + S1(T a) [[ pre: a > 0 ]] [[ pre: a > 10 ]] + { + printf ("S1::S1(T): %d\n", (int)a); + } +}; + +int main(int, char **) { + S s{-1}; + S s2{-2.5}; + + S1 s1_1{-3}; + S1 s1_2{-4.5}; + return 0; +} + +// { dg-output "default std::handle_contract_violation called: .*.C 8 S::S .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 8 S::S .*(\n|\r\n|\r)*" } +// { dg-output "S::S.T.: -1(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 8 S::S .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 8 S::S .*(\n|\r\n|\r)*" } +// { dg-output "S::S.T.: -2(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 20 S1::S1 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 20 S1::S1 .*(\n|\r\n|\r)*" } +// { dg-output "S1::S1.T.: -3(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 20 S1::S1 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 20 S1::S1 .*(\n|\r\n|\r)*" } +// { dg-output "S1::S1.T.: -4(\n|\r\n|\r)*" } + diff --git a/gcc/testsuite/g++.dg/contracts/contracts4.C b/gcc/testsuite/g++.dg/contracts/contracts4.C new file mode 100644 index 00000000000..a43fb9f98e2 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts4.C @@ -0,0 +1,11 @@ +// ensure an audit level assert with a failing predicate does not generate an +// error during runtime when the contract build level is default +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts" } + +int main() +{ + int x = 1; + [[assert audit: x < 0]]; + return 0; +} diff --git a/gcc/testsuite/g++.dg/contracts/contracts5.C b/gcc/testsuite/g++.dg/contracts/contracts5.C new file mode 100644 index 00000000000..0fa0ec83be5 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts5.C @@ -0,0 +1,13 @@ +// ensure an audit level assert with a failing predicate generates an error +// during runtime when the contract build level is audit +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts -fcontract-build-level=audit" } +// { dg-shouldfail "assert violation" } +// { dg-output "default std::handle_contract_violation called" } + +int main() +{ + int x = 1; + [[assert audit: x < 0]]; + return 0; +} diff --git a/gcc/testsuite/g++.dg/contracts/contracts6.C b/gcc/testsuite/g++.dg/contracts/contracts6.C new file mode 100644 index 00000000000..59c010e5d39 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts6.C @@ -0,0 +1,11 @@ +// ensure a default level assert with a failing predicate does not generate an +// error during runtime when the contract build level is off +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts -fcontract-build-level=off" } + +int main() +{ + int x = 1; + [[assert default: x < 0]]; + return 0; +} diff --git a/gcc/testsuite/g++.dg/contracts/contracts7.C b/gcc/testsuite/g++.dg/contracts/contracts7.C new file mode 100644 index 00000000000..eaad8f0fc9d --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts7.C @@ -0,0 +1,14 @@ +// ensure a default level assert with a failing predicate does generates an +// error during runtime but lets the program continue and complete +// successfully when the contract build level is default but continuation on +// contract failure is switched on +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts -fcontract-continuation-mode=on" } +// { dg-output "default std::handle_contract_violation called" } + +int main() +{ + int x = 1; + [[assert default: x < 0]]; + return 0; +} diff --git a/gcc/testsuite/g++.dg/contracts/contracts8.C b/gcc/testsuite/g++.dg/contracts/contracts8.C new file mode 100644 index 00000000000..9c8a6b5d8c4 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts8.C @@ -0,0 +1,43 @@ +// generic assert contract parsing checks +// ensure that existing generalized attribute parsing is not intefered with +// ensure that an assert contract cannot chain into an empty attribute list +// ensure that an attribute list cannot chain into an assert contract +// { dg-do compile } +// { dg-options "-std=c++2a -fcontracts" } + +constexpr bool fun(int x) { + return x < 0; +} + +namespace tns { + constexpr bool f(int x) { + return x < 0; + } +} + +bool assert(int x) { + return x < 0; +} + +int main() +{ + constexpr int x = 1; + [[fun(x)]]; // { dg-warning "attributes at the beginning of statement are ignored" } + [[fun(x), assert(x)]]; // { dg-warning "attributes at the beginning of statement are ignored" } + + [[assert default: fun(x), ]]; // { dg-error "expected ']'" } + [[assert default: fun(x) ]]; + + [[fun(x), assert default: fun(x)]]; // { dg-error "expected .]. before .default." } + // { dg-warning "attributes at the beginning of statement are ignored" "" { target *-*-* } .-1 } + [[fun(x), assert: fun(x)]]; // { dg-error "expected .]. before .:. token" } + // { dg-warning "attributes at the beginning of statement are ignored" "" { target *-*-* } .-1 } + [[fun(x), assert fun(x)]]; // { dg-error "expected .]. before .fun." } + // { dg-warning "attributes at the beginning of statement are ignored" "" { target *-*-* } .-1 } + [[ using tns: f(x) ]]; // { dg-warning "attributes at the beginning of statement are ignored" } + [[ using tns: f(x), assert default: fun(x) ]]; // { dg-error "expected .]. before .default." } + // { dg-warning "attributes at the beginning of statement are ignored" "" { target *-*-* } .-1 } + [[ using tns: f(x), , default: fun(x) ]]; // { dg-error "expected .]. before .:." } + // { dg-warning "attributes at the beginning of statement are ignored" "" { target *-*-* } .-1 } + return 0; +} diff --git a/gcc/testsuite/g++.dg/contracts/contracts9.C b/gcc/testsuite/g++.dg/contracts/contracts9.C new file mode 100644 index 00000000000..f566628ec44 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/contracts9.C @@ -0,0 +1,45 @@ +// ensure that dependent and non-dependent asserts inside templated +// functions parse without error whether the function is instatiated or not +// ensure that assert contract checks are generated inside called templates +// ensure that template functions can be used as assert predicates +// { dg-do run } +// { dg-options "-std=c++2a -fcontracts -fcontract-continuation-mode=on" } + +template +int fun1(int a, T b) +{ + [[ assert: a > 0 ]]; + [[ assert: (long long)b > 0 ]]; + return a > 0; +} + +template +struct test +{ + static int fun(int a, T b) { + [[ assert: a > 0 ]]; + [[ assert: b > 0 ]]; + return a > 0; + } +}; + +int main() +{ + fun1(1, -1); + fun1(-1, 1.0); + fun1(-1, "test"); + + [[ assert: fun1(-1, -5) ]]; + [[ assert: test::fun(10, -6) ]]; + [[ assert: test::fun(10.0, -7) ]]; + // return 0; +} + +// { dg-output "default std::handle_contract_violation called: .*.C 12 fun1 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 11 fun1 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 11 fun1 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 11 fun1 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 12 fun1 .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 32 main .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 21 test::fun .*(\n|\r\n|\r)*" } +// { dg-output "default std::handle_contract_violation called: .*.C 21 test::fun .*(\n|\r\n|\r)*" } diff --git a/gcc/testsuite/g++.dg/contracts/except_preload_handler/assert_fail.cpp b/gcc/testsuite/g++.dg/contracts/except_preload_handler/assert_fail.cpp new file mode 100644 index 00000000000..3dda7ac84d5 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/except_preload_handler/assert_fail.cpp @@ -0,0 +1,20 @@ +#include +#include + +int fun() { + int x = 0; + [[ assert: x < 0 ]]; + + return 0; +} + +int main(int argc, char**) { + try { + fun(); + } catch(int &ex) { + std::cerr << "synth caught indirect: " << ex << std::endl; + } + + return 0; +} + diff --git a/gcc/testsuite/g++.dg/contracts/except_preload_handler/handle_contract_violation.cpp b/gcc/testsuite/g++.dg/contracts/except_preload_handler/handle_contract_violation.cpp new file mode 100644 index 00000000000..ec051b39e5f --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/except_preload_handler/handle_contract_violation.cpp @@ -0,0 +1,14 @@ +#include +#include + +void handle_contract_violation(const std::contract_violation &violation) { + std::cerr << "custom handle_contract_violation: " << std::endl + << " line_number: " << violation.line_number() << std::endl + << " file_name: " << violation.file_name() << std::endl + << " function_name: " << violation.function_name() << std::endl + << " comment: " << violation.comment() << std::endl + << " assertion_level: " << violation.assertion_level() << std::endl + << std::endl; + throw -1; +} + diff --git a/gcc/testsuite/g++.dg/contracts/noexcept_preload_handler/assert_fail.cpp b/gcc/testsuite/g++.dg/contracts/noexcept_preload_handler/assert_fail.cpp new file mode 100644 index 00000000000..8ae98fbe668 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/noexcept_preload_handler/assert_fail.cpp @@ -0,0 +1,20 @@ +#include +#include + +int fun() noexcept { + int x = 0; + [[ assert: x < 0 ]]; + + return 0; +} + +int main(int argc, char**) { + try { + fun(); + } catch(int &ex) { + std::cerr << "synth caught indirect: " << ex << std::endl; + } + + return 0; +} + diff --git a/gcc/testsuite/g++.dg/contracts/noexcept_preload_handler/handle_contract_violation.cpp b/gcc/testsuite/g++.dg/contracts/noexcept_preload_handler/handle_contract_violation.cpp new file mode 100644 index 00000000000..ec051b39e5f --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/noexcept_preload_handler/handle_contract_violation.cpp @@ -0,0 +1,14 @@ +#include +#include + +void handle_contract_violation(const std::contract_violation &violation) { + std::cerr << "custom handle_contract_violation: " << std::endl + << " line_number: " << violation.line_number() << std::endl + << " file_name: " << violation.file_name() << std::endl + << " function_name: " << violation.function_name() << std::endl + << " comment: " << violation.comment() << std::endl + << " assertion_level: " << violation.assertion_level() << std::endl + << std::endl; + throw -1; +} + diff --git a/gcc/testsuite/g++.dg/contracts/preload_handler/assert_fail.cpp b/gcc/testsuite/g++.dg/contracts/preload_handler/assert_fail.cpp new file mode 100644 index 00000000000..0f807db0628 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/preload_handler/assert_fail.cpp @@ -0,0 +1,7 @@ +int main(int, char**) { + int x = 0; + [[ assert: x < 0 ]]; + + return 0; +} + diff --git a/gcc/testsuite/g++.dg/contracts/preload_handler/handle_contract_violation.cpp b/gcc/testsuite/g++.dg/contracts/preload_handler/handle_contract_violation.cpp new file mode 100644 index 00000000000..6029875ee6c --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/preload_handler/handle_contract_violation.cpp @@ -0,0 +1,15 @@ +#include +#include + +void handle_contract_violation(const std::contract_violation &violation) { + std::cerr << "custom handle_contract_violation: " << std::endl + << " line_number: " << violation.line_number() << std::endl + << " file_name: " << violation.file_name() << std::endl + << " function_name: " << violation.function_name() << std::endl + << " comment: " << violation.comment() << std::endl + << " assertion_level: " << violation.assertion_level() << std::endl + << " assertion_role: " << violation.assertion_role() << std::endl + << " continuation_mode: " << (int)violation.continuation_mode() << std::endl + << std::endl; +} + diff --git a/gcc/testsuite/g++.dg/contracts/preload_nocontinue_handler/assert_fail.cpp b/gcc/testsuite/g++.dg/contracts/preload_nocontinue_handler/assert_fail.cpp new file mode 100644 index 00000000000..a17a6239bb7 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/preload_nocontinue_handler/assert_fail.cpp @@ -0,0 +1,10 @@ +#include + +int main(int, char**) { + int x = 0; + [[ assert: x < 0 ]]; + + std::cout << "returning from main" << std::endl; + return 0; +} + diff --git a/gcc/testsuite/g++.dg/contracts/preload_nocontinue_handler/handle_contract_violation.cpp b/gcc/testsuite/g++.dg/contracts/preload_nocontinue_handler/handle_contract_violation.cpp new file mode 100644 index 00000000000..8499483c86b --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/preload_nocontinue_handler/handle_contract_violation.cpp @@ -0,0 +1,13 @@ +#include +#include + +void handle_contract_violation(const std::contract_violation &violation) { + std::cerr << "custom handle_contract_violation: " << std::endl + << " line_number: " << violation.line_number() << std::endl + << " file_name: " << violation.file_name() << std::endl + << " function_name: " << violation.function_name() << std::endl + << " comment: " << violation.comment() << std::endl + << " assertion_level: " << violation.assertion_level() << std::endl + << std::endl; +} + diff --git a/gcc/testsuite/g++.dg/contracts/preload_nocontinue_handler/nocontinue.cpp b/gcc/testsuite/g++.dg/contracts/preload_nocontinue_handler/nocontinue.cpp new file mode 100644 index 00000000000..2a7d53e353d --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/preload_nocontinue_handler/nocontinue.cpp @@ -0,0 +1,19 @@ +#include +#include +#include + +using handler = void (*)(const std::contract_violation &); +constexpr const char *mangledHandlerName = "_Z25handle_contract_violationRKSt18contract_violation"; +void handle_contract_violation(const std::contract_violation &violation) { + try { + handler original_handle_contract_violation; + original_handle_contract_violation = + (handler)dlsym(RTLD_NEXT, mangledHandlerName); + (*original_handle_contract_violation)(violation); + } + catch(...) { + ; // squash + } + std::terminate(); +} + diff --git a/gcc/testsuite/g++.dg/modules/contracts-1_a.C b/gcc/testsuite/g++.dg/modules/contracts-1_a.C new file mode 100644 index 00000000000..f991ef8644e --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/contracts-1_a.C @@ -0,0 +1,46 @@ +// Basic test to ensure that guarded templates correctly serialize and +// deserialize their contracts through the CMI. +// { dg-additional-options "-fmodules-ts -fcontracts -fcontract-continuation-mode=on" } +module; +#include +#include +export module foo; +// { dg-module-cmi foo } + +export int violation_count{0}; +extern "C++" export void handle_contract_violation(const std::experimental::contract_violation &violation) +{ + violation_count++; + printf("violation_count: %d\n", violation_count); +} + +export int nontemplate(int n) + [[ pre: n > 0 ]] + [[ post r: r > 0 ]] +{ + return -n; +} + +export +template +T fn(T n) + [[ pre: n > 0 ]] + [[ post r: r > 0 ]] +{ + printf("%s(%d)\n", __FUNCTION__, n); + return n; +} + +export +template +void void_fn(T n) + [[ pre: n < 0 ]] +{ + printf("%s(%d)\n", __FUNCTION__, n); +} + +export void foo_fn() +{ + fn(5); +} + diff --git a/gcc/testsuite/g++.dg/modules/contracts-1_b.C b/gcc/testsuite/g++.dg/modules/contracts-1_b.C new file mode 100644 index 00000000000..30c15f6928b --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/contracts-1_b.C @@ -0,0 +1,33 @@ +// { dg-module-do run } +// { dg-additional-options "-fmodules-ts -fcontracts -fcontract-continuation-mode=on" } +module; +#include +export module bar; +// { dg-module-cmi bar } +import foo; + +template +bool bar_fn_pre(T n) { printf("bar fn pre(%d)\n", n); return true; } + +export +template +T bar_fn(T n) + [[ pre: bar_fn_pre(n) && n > 0 ]] +{ + printf("%s(%d)\n", __FUNCTION__, n); + return n; +} + +int main(int, char**) +{ + nontemplate(5); + nontemplate(-5); + fn(5); + fn(-5); + void_fn(5); + void_fn(-5); + bar_fn(5); + bar_fn(-5); + return violation_count == 6 ? 0 : -1; +} + diff --git a/gcc/testsuite/g++.dg/modules/contracts-2_a.C b/gcc/testsuite/g++.dg/modules/contracts-2_a.C new file mode 100644 index 00000000000..828d680d2a0 --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/contracts-2_a.C @@ -0,0 +1,49 @@ +// Basic test to ensure that guarded function contracts are correctly +// serialized and deserialized through the CMI. +// This also tries to ensure that entities referenced in a function's +// contracts are correctly marked as a dependency of the function itself and +// serialized in the correct order. +// { dg-additional-options "-fmodules-ts -fcontracts -fcontract-role=default:maybe,maybe,ignore" } +module; +#include +#include +export module foo; +// { dg-module-cmi foo } + +export int violation_count{0}; +export int violation_line_sum{0}; +extern "C++" export void handle_contract_violation(const std::experimental::contract_violation &violation) +{ + violation_count++; + violation_line_sum += violation.line_number () * violation_count; + printf("violation: %d %d\n", violation_count, violation_line_sum); +} + +export int fn2(int n); +export int fn_in2(int n); +export int pre_print(int n) { printf("pre_print(%d)\n", n); return n; } + +export int fn_in1(int n) [[ pre: pre_print(n) > 0 ]] +{ + printf("%s blah (%d)\n", __FUNCTION__, n); + return n; +} +export int fn_in2(int x) [[ pre: pre_print(x) > 0 ]] +{ + printf("%s(%d)\n", __FUNCTION__, x); + return x; +} + +export int fn_iso(int n); + +export int fn1(int n) + [[ pre: pre_print(n) > 0 ]]; + +export int fn2(int n) + [[ pre: pre_print(n) > 0 ]]; + +export int pre_print2(int n); + +export int fn3(int n) + [[ pre: pre_print2(n) > 0 ]]; + diff --git a/gcc/testsuite/g++.dg/modules/contracts-2_b.C b/gcc/testsuite/g++.dg/modules/contracts-2_b.C new file mode 100644 index 00000000000..01939aeb947 --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/contracts-2_b.C @@ -0,0 +1,35 @@ +// { dg-additional-options "-fmodules-ts -fcontracts -fcontract-role=default:maybe,maybe,ignore" } +module; +#include +module foo; + +int fn1(int x) +{ + printf("%s(%d)\n", __FUNCTION__, x); + return x; +} + +int fn_iso(int n) [[ pre: pre_print(n) > 0 ]] +{ + printf("%s(%d)\n", __FUNCTION__, n); + return n; +} + +int pre_print2(int n) +{ + printf("pre_print(%d)\n", n); + return n; +} + +int fn2(int x) +{ + printf("%s(%d)\n", __FUNCTION__, x); + return x; +} + +int fn3(int x) +{ + printf("%s(%d)\n", __FUNCTION__, x); + return x; +} + diff --git a/gcc/testsuite/g++.dg/modules/contracts-2_c.C b/gcc/testsuite/g++.dg/modules/contracts-2_c.C new file mode 100644 index 00000000000..58938e0e6de --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/contracts-2_c.C @@ -0,0 +1,22 @@ +// { dg-module-do run } +// { dg-additional-options "-fmodules-ts -fcontracts -fcontract-role=default:maybe,maybe,ignore" } +import foo; + +int main(int, char**) +{ + int x = -1; + + fn_iso(x--); + fn1(x--); + fn2(x--); + fn3(x--); + fn_in1(x--); + fn_in2(x--); + return (violation_count == 6 && violation_line_sum == 729) ? 0 : -1; +} + +// TODO does any of this actually get verified? +// { dg-output "pre_print.-1.(\n|\r\n|\r)*" } +// { dg-output "violation: 1 12(\n|\r\n|\r)*" } +// { dg-output "fn_iso.-1.(\n|\r\n|\r)*" } + diff --git a/gcc/testsuite/g++.dg/modules/contracts-3_a.C b/gcc/testsuite/g++.dg/modules/contracts-3_a.C new file mode 100644 index 00000000000..a4f03d35842 --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/contracts-3_a.C @@ -0,0 +1,41 @@ +// Basic test to ensure that guarded templates correctly serialize and +// deserialize their contracts through the CMI. +// { dg-additional-options "-fmodules-ts -fcontracts -fcontract-continuation-mode=on" } +module; +#include +#include +export module foo; +// { dg-module-cmi foo } + +export int violation_count{0}; +extern "C++" export void handle_contract_violation(const std::experimental::contract_violation &violation) +{ + violation_count++; + printf("violation_count: %d\n", violation_count); +} + +export int nontemplate(int n) + [[ pre: n > 0 ]] + [[ post r: r > 0 ]] +{ + return -n; +} + +export +template +T fn(T n) + [[ pre: n > 0 ]] + [[ post r: r > 0 ]] +{ + printf("%s(%d)\n", __FUNCTION__, n); + return n; +} + +export +template +void void_fn(T n) + [[ pre: n < 0 ]] +{ + printf("%s(%d)\n", __FUNCTION__, n); +} + diff --git a/gcc/testsuite/g++.dg/modules/contracts-3_b.C b/gcc/testsuite/g++.dg/modules/contracts-3_b.C new file mode 100644 index 00000000000..b1d6375391b --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/contracts-3_b.C @@ -0,0 +1,35 @@ +// { dg-module-do run } +// { dg-additional-options "-fmodules-ts -fcontracts -fcontract-role=default:ignore,ignore,ignore" } +module; +#include +export module bar; +// { dg-module-cmi bar } +import foo; + +template +bool bar_fn_pre(T n) { printf("bar fn pre(%d)\n", n); return true; } + +export +template +T bar_fn(T n) + [[ pre: bar_fn_pre(n) && n > 0 ]] +{ + printf("%s(%d)\n", __FUNCTION__, n); + return n; +} + +int main(int, char**) +{ + nontemplate(5); + nontemplate(-5); + fn(5); + fn(-5); + fn(5.3); + fn(-5.3); + void_fn(5); + void_fn(-5); + bar_fn(5); + bar_fn(-5); + return violation_count == 7 ? 0 : -1; +} + diff --git a/gcc/testsuite/g++.dg/modules/contracts-4_a.C b/gcc/testsuite/g++.dg/modules/contracts-4_a.C new file mode 100644 index 00000000000..f269e6c2078 --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/contracts-4_a.C @@ -0,0 +1,28 @@ +// Test that template contracts are not reintpreted when the reinterpret +// contracts flag is not set, regardless of the current TU's contract +// configuration. +// { dg-additional-options "-fmodules-ts -fcontracts -fcontract-role=default:maybe,maybe,ignore" } +module; +#include +#include +export module foo; +// { dg-module-cmi foo } + +export int violation_count{0}; +extern "C++" export void handle_contract_violation(const std::experimental::contract_violation &violation) +{ + violation_count++; + printf("violation_count: %d\n", violation_count); +} + +export template +T fn_t(T t) + [[ pre: t > 0 ]] + [[ pre audit %custom: t > 0 ]] +{ + printf("%s(%d)\n", __FUNCTION__, t); + return t; +} + +export int fn_int(int n); + diff --git a/gcc/testsuite/g++.dg/modules/contracts-4_b.C b/gcc/testsuite/g++.dg/modules/contracts-4_b.C new file mode 100644 index 00000000000..65b9287b211 --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/contracts-4_b.C @@ -0,0 +1,8 @@ +// { dg-additional-options "-fmodules-ts -fcontracts -fcontract-role=default:ignore,ignore,ignore" } +module foo; + +int fn_int(int n) +{ + return fn_t(n); +} + diff --git a/gcc/testsuite/g++.dg/modules/contracts-4_c.C b/gcc/testsuite/g++.dg/modules/contracts-4_c.C new file mode 100644 index 00000000000..84fcb616314 --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/contracts-4_c.C @@ -0,0 +1,9 @@ +// { dg-additional-options "-fmodules-ts -fcontracts -fcontract-role=default:ignore,ignore,ignore" } +export module bar; +import foo; + +export int bar_fn_int(int n) +{ + return fn_t(n); +} + diff --git a/gcc/testsuite/g++.dg/modules/contracts-4_d.C b/gcc/testsuite/g++.dg/modules/contracts-4_d.C new file mode 100644 index 00000000000..dc56251d1d8 --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/contracts-4_d.C @@ -0,0 +1,22 @@ +// { dg-module-do run } +// { dg-additional-options "-fmodules-ts -fcontracts" } +module; +#include +export module baz; +import foo; +import bar; + +int main(int, char**) +{ + int x = -1; + + printf("calling fn_int\n"); + fn_int(x--); + printf("calling bar_fn_int\n"); + bar_fn_int(x--); + + return violation_count - 4; +} + +// TODO verify dg-output as well once the testsuite supports it + diff --git a/gcc/testsuite/g++.dg/modules/contracts-tpl-friend-1_a.C b/gcc/testsuite/g++.dg/modules/contracts-tpl-friend-1_a.C new file mode 100644 index 00000000000..5e6d848aaa4 --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/contracts-tpl-friend-1_a.C @@ -0,0 +1,17 @@ +// { dg-additional-options "-fmodules-ts -fcontracts" } + +export module foo; +// { dg-module-cmi foo } + +void foo (int, void *); +void foo (float, void *); + +template class TPL +{ + friend void foo (T, void *); // { dg-warning "non-template function" } + + T member; +}; + +template class TPL; // instantiate + diff --git a/gcc/testsuite/g++.dg/modules/contracts-tpl-friend-1_b.C b/gcc/testsuite/g++.dg/modules/contracts-tpl-friend-1_b.C new file mode 100644 index 00000000000..f872c9248d9 --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/contracts-tpl-friend-1_b.C @@ -0,0 +1,19 @@ +// { dg-additional-options "-fmodules-ts -fcontracts" } + +module foo; + +void foo (int x, void *p) + [[ pre: x > 0 ]] +{ + auto *obj = reinterpret_cast *> (p); + + obj->member = x; +} + +void foo (float x, void *p) + [[ pre: x > 0 ]] +{ + auto *obj = reinterpret_cast *> (p); + + obj->member = x; +} diff --git a/gcc/cp/Make-lang.in b/gcc/cp/Make-lang.in index 291835d326e..af25bdc044a 100644 --- a/gcc/cp/Make-lang.in +++ b/gcc/cp/Make-lang.in @@ -89,7 +89,7 @@ CXX_AND_OBJCXX_OBJS = \ cp/call.o cp/class.o cp/constexpr.o cp/constraint.o \ cp/coroutines.o cp/cp-gimplify.o \ cp/cp-objcp-common.o cp/cp-ubsan.o \ - cp/cvt.o cp/cxx-pretty-print.o \ + cp/cvt.o cp/contracts.o cp/cxx-pretty-print.o \ cp/decl.o cp/decl2.o cp/dump.o \ cp/error.o cp/except.o cp/expr.o \ cp/friend.o cp/init.o \ diff --git a/gcc/cp/config-lang.in b/gcc/cp/config-lang.in index 9ac3dee72f4..f9e5f322fbf 100644 --- a/gcc/cp/config-lang.in +++ b/gcc/cp/config-lang.in @@ -39,6 +39,7 @@ gtfiles="\ \$(srcdir)/c-family/c-common.cc \$(srcdir)/c-family/c-format.cc \ \$(srcdir)/c-family/c-cppbuiltin.cc \$(srcdir)/c-family/c-pragma.cc \ \$(srcdir)/cp/call.cc \$(srcdir)/cp/class.cc \$(srcdir)/cp/constexpr.cc \ +\$(srcdir)/cp/contracts.cc \ \$(srcdir)/cp/constraint.cc \$(srcdir)/cp/coroutines.cc \ \$(srcdir)/cp/cp-gimplify.cc \ \$(srcdir)/cp/cp-lang.cc \$(srcdir)/cp/cp-objcp-common.cc \ diff --git a/gcc/cp/cp-tree.def b/gcc/cp/cp-tree.def index f83b4c54d43..0e07fedc8e1 100644 --- a/gcc/cp/cp-tree.def +++ b/gcc/cp/cp-tree.def @@ -587,6 +587,17 @@ DEFTREECODE (CO_YIELD_EXPR, "co_yield", tcc_expression, 2) DEFTREECODE (CO_RETURN_EXPR, "co_return", tcc_statement, 2) +/* Different flavors of contracts. + + Assertions and preconditions have two operands: a node containing + the their mode and condition. Postconditions have an additional + operand to store the optional name for the result value. + + CONTRACT_SEMANTIC has the computed behavior of the contract. */ +DEFTREECODE (ASSERTION_STMT, "assertion_stmt", tcc_statement, 3) +DEFTREECODE (PRECONDITION_STMT, "precondition_stmt", tcc_statement, 3) +DEFTREECODE (POSTCONDITION_STMT, "postcondition_stmt", tcc_statement, 4) + /* Local variables: mode:c diff --git a/gcc/testsuite/g++.dg/contracts/backtrace_handler/Makefile b/gcc/testsuite/g++.dg/contracts/backtrace_handler/Makefile new file mode 100644 index 00000000000..4f01a9506a7 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/backtrace_handler/Makefile @@ -0,0 +1,13 @@ +CXXFLAGS=--std=c++17 -g + +default: assert_fail libhandle_contract_violation.so + +run: default + LD_PRELOAD=./libhandle_contract_violation.so ./assert_fail + +./libhandle_contract_violation.so: ./handle_contract_violation.cpp + ${CXX} ${CXXFLAGS} -shared -fPIC -o $@ $< + +clean: + rm -fr ./libhandle_contract_violation.so ./assert_fail + diff --git a/gcc/testsuite/g++.dg/contracts/backtrace_handler/README b/gcc/testsuite/g++.dg/contracts/backtrace_handler/README new file mode 100644 index 00000000000..df729f0d609 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/backtrace_handler/README @@ -0,0 +1,12 @@ +build and install gcc to $prefix, then to see the raw backtrace info run: +LD_LIBRARY_PATH=$prefix/lib64 CXX=$prefix/bin/g++ make run + +for a filtered view using addr2line, see ./prettytrace.sh: +LD_LIBRARY_PATH=$prefix/lib64 CXX=$prefix/bin/g++ make run |& ./prettytrace.sh + +prettytrace.sh relies on addr2line and c++filt to lookup and demangle names, +and misc coreutils + +example_out.txt has an example of the raw output while example_pretty.txt +shows the corresponding prettified output + diff --git a/gcc/testsuite/g++.dg/contracts/backtrace_handler/example_out.txt b/gcc/testsuite/g++.dg/contracts/backtrace_handler/example_out.txt new file mode 100644 index 00000000000..903ef22f2f5 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/backtrace_handler/example_out.txt @@ -0,0 +1,12 @@ +LD_PRELOAD=./libhandle_contract_violation.so ./assert_fail +contract violation: assert_fail.cpp:3: fun1::x < 0 is false [with contract level=default] +violation occurs here: +./assert_fail[0x4011ad] +./assert_fail[0x4011e0] +./assert_fail[0x401230] +./assert_fail[0x4011f1] +./assert_fail[0x401219] +/usr/lib/libc.so.6(__libc_start_main+0xf3)[0x7f26e4fa9223] +./assert_fail[0x4010be] +[0x0] +end of violation diff --git a/gcc/testsuite/g++.dg/contracts/backtrace_handler/example_pretty.txt b/gcc/testsuite/g++.dg/contracts/backtrace_handler/example_pretty.txt new file mode 100644 index 00000000000..9d5d481a9ef --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/backtrace_handler/example_pretty.txt @@ -0,0 +1,8 @@ +LD_PRELOAD=./libhandle_contract_violation.so ./assert_fail +contract violation: assert_fail.cpp:3: fun1::x < 0 is false [with contract level=default] +violation occurs here: + assert_fail.cpp:fun1():4 + assert_fail.cpp:tns::fun2():8 + assert_fail.cpp:void fun3(int):13 + assert_fail.cpp:fun4():16 + assert_fail.cpp:main:21 diff --git a/gcc/testsuite/g++.dg/contracts/backtrace_handler/prettytrace.sh b/gcc/testsuite/g++.dg/contracts/backtrace_handler/prettytrace.sh new file mode 100755 index 00000000000..1978cd1c164 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/backtrace_handler/prettytrace.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +inViolation="false" +sed 's/^/:/' /dev/stdin | while read -r line; do + line="$(echo "$line" | sed 's/^://')" + + if [[ "${inViolation}" == "false" ]]; then + echo "$line" + if [[ -n "$(echo "$line" | grep 'violation occurs here:')" ]]; then + inViolation="true" + fi + continue + fi + + if [[ -n "$(echo "$line" | grep 'end of violation')" ]]; then + inViolation="false" + continue + fi + + addr="$(echo "$line" | sed -r 's/.*\[0x([a-f0-9]+)\]$/\1/')" + bin="$(echo "$line" | sed -r 's/^([^([]*).*/\1/')" + [[ -n "${bin}" ]] || continue + t="$(addr2line -e "$bin" "$addr" -f)" + file="$(echo "$t" | tail -1 | tr ':' '\n' | head -1)" + file="$(echo "$file" | sed -r "s:^$(pwd)/?::")" + line="$(echo "$t" | tail -1 | tr ':' '\n' | tail -1 | cut -d' ' -f1)" + func="$(echo "$t" | head -1 | c++filt)" + [[ $file != "??" ]] && echo " $file:$func:$line" +done + diff --git a/gcc/testsuite/g++.dg/contracts/except_preload_handler/Makefile b/gcc/testsuite/g++.dg/contracts/except_preload_handler/Makefile new file mode 100644 index 00000000000..8fcc5b2368a --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/except_preload_handler/Makefile @@ -0,0 +1,13 @@ +CXXFLAGS=--std=c++17 -fcontract-continuation-mode=on + +default: assert_fail libhandle_contract_violation.so + +run: default + LD_PRELOAD=./libhandle_contract_violation.so ./assert_fail + +./libhandle_contract_violation.so: ./handle_contract_violation.cpp + ${CXX} ${CXXFLAGS} -shared -fPIC -o $@ $< + +clean: + rm -fr ./*.o ./libhandle_contract_violation.so ./assert_fail + diff --git a/gcc/testsuite/g++.dg/contracts/except_preload_handler/README b/gcc/testsuite/g++.dg/contracts/except_preload_handler/README new file mode 100644 index 00000000000..cbfe48bd33f --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/except_preload_handler/README @@ -0,0 +1,13 @@ +build and install gcc to $prefix, then run: +LD_LIBRARY_PATH=$prefix/lib64 CXX=$prefix/bin/g++ make run + +this test has a custom handle_contract_violation that throws an exception +this test is built with -fcontract-continuation-mode=on + +since 1) our fun() is not marked noexcept and 2) the continue mode is set to +on, we expect the exception thrown within the contract violation handler to +propagate back up into the catch block located in main() + +expected output therefore ends in: + synth caught indirect: -1 + diff --git a/gcc/testsuite/g++.dg/contracts/noexcept_preload_handler/Makefile b/gcc/testsuite/g++.dg/contracts/noexcept_preload_handler/Makefile new file mode 100644 index 00000000000..8fcc5b2368a --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/noexcept_preload_handler/Makefile @@ -0,0 +1,13 @@ +CXXFLAGS=--std=c++17 -fcontract-continuation-mode=on + +default: assert_fail libhandle_contract_violation.so + +run: default + LD_PRELOAD=./libhandle_contract_violation.so ./assert_fail + +./libhandle_contract_violation.so: ./handle_contract_violation.cpp + ${CXX} ${CXXFLAGS} -shared -fPIC -o $@ $< + +clean: + rm -fr ./*.o ./libhandle_contract_violation.so ./assert_fail + diff --git a/gcc/testsuite/g++.dg/contracts/noexcept_preload_handler/README b/gcc/testsuite/g++.dg/contracts/noexcept_preload_handler/README new file mode 100644 index 00000000000..aa7c6dd2df6 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/noexcept_preload_handler/README @@ -0,0 +1,15 @@ +build and install gcc to $prefix, then run: +LD_LIBRARY_PATH=$prefix/lib64 CXX=$prefix/bin/g++ make run + +this test has a custom handle_contract_violation that throws an exception +this test is built with -fcontract-continuation-mode=on + +since 1) our fun() *IS* marked noexcept even though 2) the continue mode is +set to on, we expect the exception thrown within the contract violation +handler to quashed and have std::terminate run. + +expected output therefore ends in: + terminate called after throwing an instance of 'int' + +despite there being a catch(int &) handler in main + diff --git a/gcc/testsuite/g++.dg/contracts/preload_handler/Makefile b/gcc/testsuite/g++.dg/contracts/preload_handler/Makefile new file mode 100644 index 00000000000..6ff083f4b5c --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/preload_handler/Makefile @@ -0,0 +1,13 @@ +CXXFLAGS=--std=c++17 + +default: assert_fail libhandle_contract_violation.so + +run: default + LD_PRELOAD=./libhandle_contract_violation.so ./assert_fail + +./libhandle_contract_violation.so: ./handle_contract_violation.cpp + ${CXX} ${CXXFLAGS} -shared -fPIC -o $@ $< + +clean: + rm -fr ./libhandle_contract_violation.so ./assert_fail + diff --git a/gcc/testsuite/g++.dg/contracts/preload_handler/README b/gcc/testsuite/g++.dg/contracts/preload_handler/README new file mode 100644 index 00000000000..cc913fe8188 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/preload_handler/README @@ -0,0 +1,2 @@ +build and install gcc to $prefix, then run: +LD_LIBRARY_PATH=$prefix/lib64 CXX=$prefix/bin/g++ make run diff --git a/gcc/testsuite/g++.dg/contracts/preload_nocontinue_handler/Makefile b/gcc/testsuite/g++.dg/contracts/preload_nocontinue_handler/Makefile new file mode 100644 index 00000000000..c8263285471 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/preload_nocontinue_handler/Makefile @@ -0,0 +1,23 @@ +CXXFLAGS=--std=c++17 -fcontract-continuation-mode=on +LDFLAGS=-ldl + +default: assert_fail libhandle_contract_violation.so libnocontinue.so + +run: default + ./assert_fail + +runno: default + LD_PRELOAD="./libnocontinue.so ./libhandle_contract_violation.so" ./assert_fail + +runnostd: default + LD_PRELOAD=./libnocontinue.so ./assert_fail + +./libhandle_contract_violation.so: ./handle_contract_violation.cpp + ${CXX} ${CXXFLAGS} -shared -fPIC -o $@ $< + +./libnocontinue.so: ./nocontinue.cpp + ${CXX} ${CXXFLAGS} -shared -fPIC -o $@ $< + +clean: + rm -fr ./libhandle_contract_violation.so ./libnocontinue.so ./assert_fail + diff --git a/gcc/testsuite/g++.dg/contracts/preload_nocontinue_handler/README b/gcc/testsuite/g++.dg/contracts/preload_nocontinue_handler/README new file mode 100644 index 00000000000..5c931918759 --- /dev/null +++ b/gcc/testsuite/g++.dg/contracts/preload_nocontinue_handler/README @@ -0,0 +1,23 @@ +build and install gcc to $prefix, then run: + +LD_LIBRARY_PATH=$prefix/lib64 CXX=$prefix/bin/g++ make run + Build and run with continuation mode on; will print violation info from the + standard handler and then continue to print "returning from main" + +LD_LIBRARY_PATH=$prefix/lib64 CXX=$prefix/bin/g++ make runnostd + Build and run with continuation mode on, using the default violation handler + while preloading the 'nocontinue' hook. This uses LD_PRELOAD to turn all + continuing contract violations into non-continuing versions. + + Will print violation info from the standard handler and then terminate -- it + will not print "returning from main" + +LD_LIBRARY_PATH=$prefix/lib64 CXX=$prefix/bin/g++ make runno + Build and run with continuation mode on, using a custom violation handler + while preloading the 'nocontinue' hook. This uses LD_PRELOAD to turn all + continuing contract violations into non-continuing versions and to install a + custom violation handler. + + Will print violation info from the custom handler and then terminate -- it + will not print "returning from main" + diff --git a/gcc/testsuite/g++.dg/modules/modules.exp b/gcc/testsuite/g++.dg/modules/modules.exp index afb323d0efd..45ff74c6aa9 100644 --- a/gcc/testsuite/g++.dg/modules/modules.exp +++ b/gcc/testsuite/g++.dg/modules/modules.exp @@ -187,8 +187,9 @@ proc module_do_it { do_what testcase std asm_list } { lappend options "additional_flags=$std" set ident "$ident $std" } - if { [llength $do_what] > 3 } { - lappend options "additional_flags=[lindex $do_what 3]" + global extra_tool_flags + if { [llength $extra_tool_flags] } { + lappend options "additional_flags=$extra_tool_flags" } set execname "./[file tail $testcase].exe" @@ -250,6 +251,8 @@ proc module-init { src } { set option_list {} set have_std 0 set std_prefix "-std=c++" + global extra_tool_flags + set extra_tool_flags {} foreach op $tmp { switch [lindex $op 0] { @@ -258,11 +261,13 @@ proc module-init { src } { if { [string match "*-std=*" [lindex $op 2]] } { set have_std 1 } + eval lappend extra_tool_flags [lindex $op 2] } "dg-additional-options" { if { [string match "*-std=*" [lindex $op 2]] } { set have_std 1 } + eval lappend extra_tool_flags [lindex $op 2] } } } diff --git a/gcc/testsuite/lib/g++.exp b/gcc/testsuite/lib/g++.exp index 16e61fb4ad4..4e10b614f07 100644 --- a/gcc/testsuite/lib/g++.exp +++ b/gcc/testsuite/lib/g++.exp @@ -142,6 +142,10 @@ proc g++_link_flags { paths } { append flags " -L${gccpath}/libstdc++-v3/src/.libs " append ld_library_path ":${gccpath}/libstdc++-v3/src/.libs" } + if [file exists "${gccpath}/libstdc++-v3/src/experimental/.libs/libstdc++exp.a"] { + append flags " -L${gccpath}/libstdc++-v3/src/experimental/.libs " + append ld_library_path ":${gccpath}/libstdc++-v3/src/experimental/.libs" + } if [file exists "${gccpath}/libiberty/libiberty.a"] { append flags "-L${gccpath}/libiberty " base-commit: e724b0480bfa5ec04f39be8c7290330b495c59de prerequisite-patch-id: f61f3a2dfb3aba4333bc5ff62c166acb2f86451e prerequisite-patch-id: aab00ff2622d5b1b24727005363b12c77b338047 -- 2.31.1