From patchwork Wed Mar 29 18:16:02 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Kees Cook X-Patchwork-Id: 9652243 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id C75B3602C8 for ; Wed, 29 Mar 2017 18:19:35 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id AEC2228517 for ; Wed, 29 Mar 2017 18:19:35 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id A34932851B; Wed, 29 Mar 2017 18:19:35 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-1.9 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID autolearn=unavailable version=3.3.1 Received: from bombadil.infradead.org (bombadil.infradead.org [65.50.211.133]) (using TLSv1.2 with cipher AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id D7FE228517 for ; Wed, 29 Mar 2017 18:19:33 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20170209; h=Sender: Content-Transfer-Encoding:Content-Type:MIME-Version:Cc:List-Subscribe: List-Help:List-Post:List-Archive:List-Unsubscribe:List-Id:References: In-Reply-To:Message-Id:Date:Subject:To:From:Reply-To:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Owner; bh=S1LdaAC0BGUJCr+BI3vSo5VZTKRNv23F7rGVmvsWngk=; b=Tj1GYZsg47mIJptauUJYx8J21s WIzfijZ1WocPQ2IWhm7OtFz/2oa5BQ2os6OlR5ldC0RTh5RO5TBEeLCv0qwYDHPfltcKt7kS7Uybt wbmRh1WZ++TKpuahsvFwKztxtXFoLvWeyq+vyQccDpheenQOf0l++KLbb9JnKs9QipwjgrHznDJsH 9jELzQFk3TZ1/GMYSIG1a/WurqgExRFzZ8V1xyQ5hYGtucZAU3cW7CAeJn8hmth99fUBFC4y/i6UW JkfqfGMaJ4WkeOWSjEbSq6Sfv40h7x3FFsnhkB1alqD7QuWSgyjfWWMAbhKFra1u81w9romCwqmyL Fp77B2Xw==; Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.87 #1 (Red Hat Linux)) id 1ctIBp-0008A1-Fh; Wed, 29 Mar 2017 18:19:25 +0000 Received: from mail-pg0-x236.google.com ([2607:f8b0:400e:c05::236]) by bombadil.infradead.org with esmtps (Exim 4.87 #1 (Red Hat Linux)) id 1ctI9G-00058Q-6k for linux-arm-kernel@lists.infradead.org; Wed, 29 Mar 2017 18:16:59 +0000 Received: by mail-pg0-x236.google.com with SMTP id 21so14690465pgg.1 for ; Wed, 29 Mar 2017 11:16:26 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=3VWWMWYqsUCTeqI51RwjVZIjKQlY2AOvqEi5woJ34Ls=; b=SPMqXGp+OoSY2Pz5GEaFsTB4Cp0/QPh/wyulQabC2SFen52KyX8u3J+YZSnIpXk1EO 9KfuVERgXcHBGNG3RApTeFXll6VA0t2BN89tqkgnl5NOpX6R2hM2bA7ucRL14IicrD6H tXZTnPWQIbBl9yYUbIDxyhjPkSsxTa7C340Aw= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=3VWWMWYqsUCTeqI51RwjVZIjKQlY2AOvqEi5woJ34Ls=; b=i2u6qPHCGdceFKqUdB2jTonWmQ9FatHUqXTMuKoX7erCVKTji7oV4SNOznAGWFnExa wMIyUgRy+QCx+u7erpvwG3Pp99AivVeZoM4J1tawtuCt72mNHfkX+yAcW5vZ+NFOHOuq igIXlHifhXtJ4DZt2B6/6CRiwM3wyRt33R1C4KzefGKLYLFyPAsk3wDtdGr76P0jKwWH ulenXbcjCz+E22EIr9o9bR5WT8m2mQbv+duXVo28pb2v/+s2BH9K97DZ6wRQ+nD9BZ40 asHrOCRiutYownoJD3auS41/PDgPtn3Y7XVMdDxpckBgBY+85vLTLnTeex6r4yjbSoZo qd5g== X-Gm-Message-State: AFeK/H0OwEFzrORnrmhvXhPTyauwHltTKxNH//nvFVPL4Wo/EMd1D3VLv97flWI6AVSJ+lYY X-Received: by 10.98.39.134 with SMTP id n128mr1834281pfn.17.1490811386019; Wed, 29 Mar 2017 11:16:26 -0700 (PDT) Received: from www.outflux.net (173-164-112-133-Oregon.hfc.comcastbusiness.net. [173.164.112.133]) by smtp.gmail.com with ESMTPSA id x68sm14877762pfx.45.2017.03.29.11.16.22 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Wed, 29 Mar 2017 11:16:24 -0700 (PDT) From: Kees Cook To: kernel-hardening@lists.openwall.com Subject: [RFC v2][PATCH 10/11] gcc-plugins: Add constify plugin Date: Wed, 29 Mar 2017 11:16:02 -0700 Message-Id: <1490811363-93944-11-git-send-email-keescook@chromium.org> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1490811363-93944-1-git-send-email-keescook@chromium.org> References: <1490811363-93944-1-git-send-email-keescook@chromium.org> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20170329_111646_464460_5660B7EF X-CRM114-Status: GOOD ( 23.56 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Mark Rutland , Hoeun Ryu , Kees Cook , x86@kernel.org, Russell King , linux-kernel@vger.kernel.org, Emese Revfy , Andy Lutomirski , PaX Team , linux-arm-kernel@lists.infradead.org MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Virus-Scanned: ClamAV using ClamSMTP This is a port of the PaX/grsecurity constify plugin. However, it has the automatic function-pointer struct detection temporarily disabled. As a result, this will only recognize the __do_const annotation, which makes all instances of a marked structure read-only. The rare_write() infrastructure can be used to make changes to such variables. Signed-off-by: Kees Cook --- arch/Kconfig | 13 + include/linux/compiler-gcc.h | 5 + include/linux/compiler.h | 8 + scripts/Makefile.gcc-plugins | 2 + scripts/gcc-plugins/constify_plugin.c | 585 ++++++++++++++++++++++++++++++++++ 5 files changed, 613 insertions(+) create mode 100644 scripts/gcc-plugins/constify_plugin.c diff --git a/arch/Kconfig b/arch/Kconfig index 5ebf62500b99..79447d6a40bc 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -436,6 +436,19 @@ config GCC_PLUGIN_STRUCTLEAK_VERBOSE initialized. Since not all existing initializers are detected by the plugin, this can produce false positive warnings. +config GCC_PLUGIN_CONSTIFY + bool "Make function pointer structures and marked variables read-only" + depends on GCC_PLUGINS && HAVE_ARCH_RARE_WRITE && !UML + help + By saying Y here the compiler will automatically constify a class + of types that contain only function pointers, as well as any + manually annotated structures. This reduces the kernel's attack + surface and also produces a better memory layout. + + This plugin was ported from grsecurity/PaX. More information at: + * https://grsecurity.net/ + * https://pax.grsecurity.net/ + config HAVE_CC_STACKPROTECTOR bool help diff --git a/include/linux/compiler-gcc.h b/include/linux/compiler-gcc.h index 0efef9cf014f..cee2bf7c32a4 100644 --- a/include/linux/compiler-gcc.h +++ b/include/linux/compiler-gcc.h @@ -191,6 +191,11 @@ #if GCC_VERSION >= 40500 +#ifdef CONSTIFY_PLUGIN +#define __no_const __attribute__((no_const)) +#define __do_const __attribute__((do_const)) +#endif + #ifndef __CHECKER__ #ifdef LATENT_ENTROPY_PLUGIN #define __latent_entropy __attribute__((latent_entropy)) diff --git a/include/linux/compiler.h b/include/linux/compiler.h index 274bd03cfe9e..2def0f12a71c 100644 --- a/include/linux/compiler.h +++ b/include/linux/compiler.h @@ -476,6 +476,14 @@ static __always_inline void __write_once_size(volatile void *p, void *res, int s # define __latent_entropy #endif +#ifndef __do_const +# define __do_const +#endif + +#ifndef __no_const +# define __no_const +#endif + /* * Tell gcc if a function is cold. The compiler will assume any path * directly leading to the call is unlikely. diff --git a/scripts/Makefile.gcc-plugins b/scripts/Makefile.gcc-plugins index 82335533620e..8d264c0bb758 100644 --- a/scripts/Makefile.gcc-plugins +++ b/scripts/Makefile.gcc-plugins @@ -5,6 +5,8 @@ ifdef CONFIG_GCC_PLUGINS SANCOV_PLUGIN := -fplugin=$(objtree)/scripts/gcc-plugins/sancov_plugin.so gcc-plugin-$(CONFIG_GCC_PLUGIN_CYC_COMPLEXITY) += cyc_complexity_plugin.so + gcc-plugin-$(CONFIG_GCC_PLUGIN_CONSTIFY) += constify_plugin.so + gcc-plugin-cflags-$(CONFIG_GCC_PLUGIN_CONSTIFY) += -DCONSTIFY_PLUGIN gcc-plugin-$(CONFIG_GCC_PLUGIN_LATENT_ENTROPY) += latent_entropy_plugin.so gcc-plugin-cflags-$(CONFIG_GCC_PLUGIN_LATENT_ENTROPY) += -DLATENT_ENTROPY_PLUGIN diff --git a/scripts/gcc-plugins/constify_plugin.c b/scripts/gcc-plugins/constify_plugin.c new file mode 100644 index 000000000000..e7d6f3140e87 --- /dev/null +++ b/scripts/gcc-plugins/constify_plugin.c @@ -0,0 +1,585 @@ +/* + * Copyright 2011 by Emese Revfy + * Copyright 2011-2016 by PaX Team + * Licensed under the GPL v2, or (at your option) v3 + * + * This gcc plugin constifies all structures which contain only function pointers or are explicitly marked for constification. + * + * Homepage: + * http://www.grsecurity.net/~ephox/const_plugin/ + * + * Usage: + * $ gcc -I`gcc -print-file-name=plugin`/include -fPIC -shared -O2 -o constify_plugin.so constify_plugin.c + * $ gcc -fplugin=constify_plugin.so test.c -O2 + */ + +#include "gcc-common.h" + +// unused C type flag in all versions 4.5-6 +#define TYPE_CONSTIFY_VISITED(TYPE) TYPE_LANG_FLAG_4(TYPE) + +__visible int plugin_is_GPL_compatible; + +static bool enabled = true; + +static struct plugin_info const_plugin_info = { + .version = "201607241840vanilla", + .help = "disable\tturn off constification\n", +}; + +static struct { + const char *name; + const char *asm_op; +} const_sections[] = { + {".init.rodata", "\t.section\t.init.rodata,\"a\""}, + {".ref.rodata", "\t.section\t.ref.rodata,\"a\""}, + {".devinit.rodata", "\t.section\t.devinit.rodata,\"a\""}, + {".devexit.rodata", "\t.section\t.devexit.rodata,\"a\""}, + {".cpuinit.rodata", "\t.section\t.cpuinit.rodata,\"a\""}, + {".cpuexit.rodata", "\t.section\t.cpuexit.rodata,\"a\""}, + {".meminit.rodata", "\t.section\t.meminit.rodata,\"a\""}, + {".memexit.rodata", "\t.section\t.memexit.rodata,\"a\""}, + {".data..read_only", "\t.section\t.data..read_only,\"a\""}, +}; + +typedef struct { + bool has_fptr_field; + bool has_writable_field; + bool has_do_const_field; + bool has_no_const_field; +} constify_info; + +static const_tree get_field_type(const_tree field) +{ + return strip_array_types(TREE_TYPE(field)); +} + +static bool is_fptr(const_tree field) +{ + /* XXX: disable automatic constification. */ + return false; + + const_tree ptr = get_field_type(field); + + if (TREE_CODE(ptr) != POINTER_TYPE) + return false; + + return TREE_CODE(TREE_TYPE(ptr)) == FUNCTION_TYPE; +} + +/* + * determine whether the given structure type meets the requirements for automatic constification, + * including the constification attributes on nested structure types + */ +static void constifiable(const_tree node, constify_info *cinfo) +{ + const_tree field; + + gcc_assert(TREE_CODE(node) == RECORD_TYPE || TREE_CODE(node) == UNION_TYPE); + + // e.g., pointer to structure fields while still constructing the structure type + if (TYPE_FIELDS(node) == NULL_TREE) + return; + + for (field = TYPE_FIELDS(node); field; field = TREE_CHAIN(field)) { + const_tree type = get_field_type(field); + enum tree_code code = TREE_CODE(type); + + if (node == type) + continue; + + if (is_fptr(field)) + cinfo->has_fptr_field = true; + else if (code == RECORD_TYPE || code == UNION_TYPE) { + if (lookup_attribute("do_const", TYPE_ATTRIBUTES(type))) + cinfo->has_do_const_field = true; + else if (lookup_attribute("no_const", TYPE_ATTRIBUTES(type))) + cinfo->has_no_const_field = true; + else + constifiable(type, cinfo); + } else if (!TREE_READONLY(field)) + cinfo->has_writable_field = true; + } +} + +static bool constified(const_tree node) +{ + constify_info cinfo = { + .has_fptr_field = false, + .has_writable_field = false, + .has_do_const_field = false, + .has_no_const_field = false + }; + + gcc_assert(TREE_CODE(node) == RECORD_TYPE || TREE_CODE(node) == UNION_TYPE); + + if (lookup_attribute("no_const", TYPE_ATTRIBUTES(node))) { +// gcc_assert(!TYPE_READONLY(node)); + return false; + } + + if (lookup_attribute("do_const", TYPE_ATTRIBUTES(node))) { + gcc_assert(TYPE_READONLY(node)); + return true; + } + + constifiable(node, &cinfo); + if ((!cinfo.has_fptr_field || cinfo.has_writable_field || cinfo.has_no_const_field) && !cinfo.has_do_const_field) + return false; + + return TYPE_READONLY(node); +} + +static void deconstify_tree(tree node); + +static void deconstify_type(tree type) +{ + tree field; + + gcc_assert(TREE_CODE(type) == RECORD_TYPE || TREE_CODE(type) == UNION_TYPE); + + for (field = TYPE_FIELDS(type); field; field = TREE_CHAIN(field)) { + const_tree fieldtype = get_field_type(field); + + // special case handling of simple ptr-to-same-array-type members + if (TREE_CODE(TREE_TYPE(field)) == POINTER_TYPE) { + tree ptrtype = TREE_TYPE(TREE_TYPE(field)); + + if (TREE_TYPE(TREE_TYPE(field)) == type) + continue; + if (TREE_CODE(ptrtype) != RECORD_TYPE && TREE_CODE(ptrtype) != UNION_TYPE) + continue; + if (!constified(ptrtype)) + continue; + if (TYPE_MAIN_VARIANT(ptrtype) == TYPE_MAIN_VARIANT(type)) + TREE_TYPE(field) = build_pointer_type(build_qualified_type(type, TYPE_QUALS(ptrtype) & ~TYPE_QUAL_CONST)); + continue; + } + if (TREE_CODE(fieldtype) != RECORD_TYPE && TREE_CODE(fieldtype) != UNION_TYPE) + continue; + if (!constified(fieldtype)) + continue; + + deconstify_tree(field); + TREE_READONLY(field) = 0; + } + TYPE_READONLY(type) = 0; + C_TYPE_FIELDS_READONLY(type) = 0; + if (lookup_attribute("do_const", TYPE_ATTRIBUTES(type))) { + TYPE_ATTRIBUTES(type) = copy_list(TYPE_ATTRIBUTES(type)); + TYPE_ATTRIBUTES(type) = remove_attribute("do_const", TYPE_ATTRIBUTES(type)); + } +} + +static void deconstify_tree(tree node) +{ + tree old_type, new_type, field; + + old_type = TREE_TYPE(node); + while (TREE_CODE(old_type) == ARRAY_TYPE && TREE_CODE(TREE_TYPE(old_type)) != ARRAY_TYPE) { + node = TREE_TYPE(node) = copy_node(old_type); + old_type = TREE_TYPE(old_type); + } + + gcc_assert(TREE_CODE(old_type) == RECORD_TYPE || TREE_CODE(old_type) == UNION_TYPE); + gcc_assert(TYPE_READONLY(old_type) && (TYPE_QUALS(old_type) & TYPE_QUAL_CONST)); + + new_type = build_qualified_type(old_type, TYPE_QUALS(old_type) & ~TYPE_QUAL_CONST); + TYPE_FIELDS(new_type) = copy_list(TYPE_FIELDS(new_type)); + for (field = TYPE_FIELDS(new_type); field; field = TREE_CHAIN(field)) + DECL_FIELD_CONTEXT(field) = new_type; + + deconstify_type(new_type); + + TREE_TYPE(node) = new_type; +} + +static tree handle_no_const_attribute(tree *node, tree name, tree args, int flags, bool *no_add_attrs) +{ + tree type; + constify_info cinfo = { + .has_fptr_field = false, + .has_writable_field = false, + .has_do_const_field = false, + .has_no_const_field = false + }; + + *no_add_attrs = true; + if (TREE_CODE(*node) == FUNCTION_DECL) { + error("%qE attribute does not apply to functions (%qF)", name, *node); + return NULL_TREE; + } + + if (TREE_CODE(*node) == PARM_DECL) { + error("%qE attribute does not apply to function parameters (%qD)", name, *node); + return NULL_TREE; + } + + if (TREE_CODE(*node) == VAR_DECL) { + error("%qE attribute does not apply to variables (%qD)", name, *node); + return NULL_TREE; + } + + if (TYPE_P(*node)) { + type = *node; + } else { + if (TREE_CODE(*node) != TYPE_DECL) { + error("%qE attribute does not apply to %qD (%qT)", name, *node, TREE_TYPE(*node)); + return NULL_TREE; + } + type = TREE_TYPE(*node); + } + + if (TREE_CODE(type) != RECORD_TYPE && TREE_CODE(type) != UNION_TYPE) { + error("%qE attribute used on %qT applies to struct and union types only", name, type); + return NULL_TREE; + } + + if (lookup_attribute(IDENTIFIER_POINTER(name), TYPE_ATTRIBUTES(type))) { + error("%qE attribute is already applied to the type %qT", name, type); + return NULL_TREE; + } + + if (TYPE_P(*node)) { + if (lookup_attribute("do_const", TYPE_ATTRIBUTES(type))) + error("%qE attribute used on type %qT is incompatible with 'do_const'", name, type); + else + *no_add_attrs = false; + return NULL_TREE; + } + + constifiable(type, &cinfo); + if ((cinfo.has_fptr_field && !cinfo.has_writable_field && !cinfo.has_no_const_field) || lookup_attribute("do_const", TYPE_ATTRIBUTES(type))) { + if (enabled) { + if TYPE_P(*node) + deconstify_type(*node); + else + deconstify_tree(*node); + } + if (TYPE_P(*node)) + TYPE_CONSTIFY_VISITED(*node) = 1; + else + TYPE_CONSTIFY_VISITED(TREE_TYPE(*node)) = 1; + return NULL_TREE; + } + + if (enabled && TYPE_FIELDS(type)) + error("%qE attribute used on type %qT that is not constified", name, type); + return NULL_TREE; +} + +static void constify_type(tree type) +{ + gcc_assert(type == TYPE_MAIN_VARIANT(type)); + TYPE_READONLY(type) = 1; + C_TYPE_FIELDS_READONLY(type) = 1; + TYPE_CONSTIFY_VISITED(type) = 1; +// TYPE_ATTRIBUTES(type) = copy_list(TYPE_ATTRIBUTES(type)); +// TYPE_ATTRIBUTES(type) = tree_cons(get_identifier("do_const"), NULL_TREE, TYPE_ATTRIBUTES(type)); +} + +static tree handle_do_const_attribute(tree *node, tree name, tree args, int flags, bool *no_add_attrs) +{ + *no_add_attrs = true; + if (!TYPE_P(*node)) { + error("%qE attribute applies to types only (%qD)", name, *node); + return NULL_TREE; + } + + if (TREE_CODE(*node) != RECORD_TYPE && TREE_CODE(*node) != UNION_TYPE) { + error("%qE attribute used on %qT applies to struct and union types only", name, *node); + return NULL_TREE; + } + + if (lookup_attribute(IDENTIFIER_POINTER(name), TYPE_ATTRIBUTES(*node))) { + error("%qE attribute used on %qT is already applied to the type", name, *node); + return NULL_TREE; + } + + if (lookup_attribute("no_const", TYPE_ATTRIBUTES(*node))) { + error("%qE attribute used on %qT is incompatible with 'no_const'", name, *node); + return NULL_TREE; + } + + *no_add_attrs = false; + return NULL_TREE; +} + +static struct attribute_spec no_const_attr = { + .name = "no_const", + .min_length = 0, + .max_length = 0, + .decl_required = false, + .type_required = false, + .function_type_required = false, + .handler = handle_no_const_attribute, +#if BUILDING_GCC_VERSION >= 4007 + .affects_type_identity = true +#endif +}; + +static struct attribute_spec do_const_attr = { + .name = "do_const", + .min_length = 0, + .max_length = 0, + .decl_required = false, + .type_required = false, + .function_type_required = false, + .handler = handle_do_const_attribute, +#if BUILDING_GCC_VERSION >= 4007 + .affects_type_identity = true +#endif +}; + +static void register_attributes(void *event_data, void *data) +{ + register_attribute(&no_const_attr); + register_attribute(&do_const_attr); +} + +static void finish_type(void *event_data, void *data) +{ + tree type = (tree)event_data; + constify_info cinfo = { + .has_fptr_field = false, + .has_writable_field = false, + .has_do_const_field = false, + .has_no_const_field = false + }; + + if (type == NULL_TREE || type == error_mark_node) + return; + +#if BUILDING_GCC_VERSION >= 5000 + if (TREE_CODE(type) == ENUMERAL_TYPE) + return; +#endif + + if (TYPE_FIELDS(type) == NULL_TREE || TYPE_CONSTIFY_VISITED(type)) + return; + + constifiable(type, &cinfo); + + if (lookup_attribute("no_const", TYPE_ATTRIBUTES(type))) { + if ((cinfo.has_fptr_field && !cinfo.has_writable_field && !cinfo.has_no_const_field) || cinfo.has_do_const_field) { + deconstify_type(type); + TYPE_CONSTIFY_VISITED(type) = 1; + } else + error("'no_const' attribute used on type %qT that is not constified", type); + return; + } + + if (lookup_attribute("do_const", TYPE_ATTRIBUTES(type))) { + if (!cinfo.has_writable_field && !cinfo.has_no_const_field) { + error("'do_const' attribute used on type %qT that is%sconstified", type, cinfo.has_fptr_field ? " " : " not "); + return; + } + constify_type(type); + return; + } + + if (cinfo.has_fptr_field && !cinfo.has_writable_field && !cinfo.has_no_const_field) { + if (lookup_attribute("do_const", TYPE_ATTRIBUTES(type))) { + error("'do_const' attribute used on type %qT that is constified", type); + return; + } + constify_type(type); + return; + } + + deconstify_type(type); + TYPE_CONSTIFY_VISITED(type) = 1; +} + +static bool is_constified_var(varpool_node_ptr node) +{ + tree var = NODE_DECL(node); + tree type = TREE_TYPE(var); + + if (node->alias) + return false; + + if (DECL_EXTERNAL(var)) + return false; + + // XXX handle more complex nesting of arrays/structs + if (TREE_CODE(type) == ARRAY_TYPE) + type = TREE_TYPE(type); + + if (TREE_CODE(type) != RECORD_TYPE && TREE_CODE(type) != UNION_TYPE) + return false; + + if (!TYPE_READONLY(type) || !C_TYPE_FIELDS_READONLY(type)) + return false; + + if (!TYPE_CONSTIFY_VISITED(type)) + return false; + + return true; +} + +static void check_section_mismatch(varpool_node_ptr node) +{ + tree var, section; + size_t i; + const char *name; + + var = NODE_DECL(node); + name = get_decl_section_name(var); + section = lookup_attribute("section", DECL_ATTRIBUTES(var)); + if (!section) { + if (name) { + fprintf(stderr, "DECL_SECTION [%s] ", name); + dump_varpool_node(stderr, node); + gcc_unreachable(); + } + return; + } else + gcc_assert(name); + +//fprintf(stderr, "SECTIONAME: [%s] ", get_decl_section_name(var)); +//debug_tree(var); + + gcc_assert(!TREE_CHAIN(section)); + gcc_assert(TREE_VALUE(section)); + + section = TREE_VALUE(TREE_VALUE(section)); + gcc_assert(!strcmp(TREE_STRING_POINTER(section), name)); +//debug_tree(section); + + for (i = 0; i < ARRAY_SIZE(const_sections); i++) + if (!strcmp(const_sections[i].name, name)) + return; + + error_at(DECL_SOURCE_LOCATION(var), "constified variable %qD placed into writable section %E", var, section); +} + +// this works around a gcc bug/feature where uninitialized globals +// are moved into the .bss section regardless of any constification +// see gcc/varasm.c:bss_initializer_p() +static void fix_initializer(varpool_node_ptr node) +{ + tree var = NODE_DECL(node); + tree type = TREE_TYPE(var); + + if (DECL_INITIAL(var)) + return; + + DECL_INITIAL(var) = build_constructor(type, NULL); +// inform(DECL_SOURCE_LOCATION(var), "constified variable %qE moved into .rodata", var); +} + +static void check_global_variables(void *event_data, void *data) +{ + varpool_node_ptr node; + + FOR_EACH_VARIABLE(node) { + if (!is_constified_var(node)) + continue; + + check_section_mismatch(node); + fix_initializer(node); + } +} + +static unsigned int check_local_variables_execute(void) +{ + unsigned int ret = 0; + tree var; + + unsigned int i; + + FOR_EACH_LOCAL_DECL(cfun, i, var) { + tree type = TREE_TYPE(var); + + gcc_assert(DECL_P(var)); + if (is_global_var(var)) + continue; + + if (TREE_CODE(type) != RECORD_TYPE && TREE_CODE(type) != UNION_TYPE) + continue; + + if (!TYPE_READONLY(type) || !C_TYPE_FIELDS_READONLY(type)) + continue; + + if (!TYPE_CONSTIFY_VISITED(type)) + continue; + + error_at(DECL_SOURCE_LOCATION(var), "constified variable %qE cannot be local", var); + ret = 1; + } + return ret; +} + +#define PASS_NAME check_local_variables +#define NO_GATE +#include "gcc-generate-gimple-pass.h" + +static unsigned int (*old_section_type_flags)(tree decl, const char *name, int reloc); + +static unsigned int constify_section_type_flags(tree decl, const char *name, int reloc) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(const_sections); i++) + if (!strcmp(const_sections[i].name, name)) + return 0; + + return old_section_type_flags(decl, name, reloc); +} + +static void constify_start_unit(void *gcc_data, void *user_data) +{ +// size_t i; + +// for (i = 0; i < ARRAY_SIZE(const_sections); i++) +// const_sections[i].section = get_unnamed_section(0, output_section_asm_op, const_sections[i].asm_op); +// const_sections[i].section = get_section(const_sections[i].name, 0, NULL); + + old_section_type_flags = targetm.section_type_flags; + targetm.section_type_flags = constify_section_type_flags; +} + +__visible int plugin_init(struct plugin_name_args *plugin_info, struct plugin_gcc_version *version) +{ + const char * const plugin_name = plugin_info->base_name; + const int argc = plugin_info->argc; + const struct plugin_argument * const argv = plugin_info->argv; + int i; + + struct register_pass_info check_local_variables_pass_info; + + check_local_variables_pass_info.pass = make_check_local_variables_pass(); + check_local_variables_pass_info.reference_pass_name = "ssa"; + check_local_variables_pass_info.ref_pass_instance_number = 1; + check_local_variables_pass_info.pos_op = PASS_POS_INSERT_BEFORE; + + if (!plugin_default_version_check(version, &gcc_version)) { + error(G_("incompatible gcc/plugin versions")); + return 1; + } + + for (i = 0; i < argc; ++i) { + if (!(strcmp(argv[i].key, "disable"))) { + enabled = false; + continue; + } + error(G_("unknown option '-fplugin-arg-%s-%s'"), plugin_name, argv[i].key); + } + + if (strncmp(lang_hooks.name, "GNU C", 5) && !strncmp(lang_hooks.name, "GNU C+", 6)) { + inform(UNKNOWN_LOCATION, G_("%s supports C only, not %s"), plugin_name, lang_hooks.name); + enabled = false; + } + + register_callback(plugin_name, PLUGIN_INFO, NULL, &const_plugin_info); + if (enabled) { + register_callback(plugin_name, PLUGIN_ALL_IPA_PASSES_START, check_global_variables, NULL); + register_callback(plugin_name, PLUGIN_FINISH_TYPE, finish_type, NULL); + register_callback(plugin_name, PLUGIN_PASS_MANAGER_SETUP, NULL, &check_local_variables_pass_info); + register_callback(plugin_name, PLUGIN_START_UNIT, constify_start_unit, NULL); + } + register_callback(plugin_name, PLUGIN_ATTRIBUTES, register_attributes, NULL); + + return 0; +}