| /* Pointer Bounds Checker insrumentation pass. |
| Copyright (C) 2014-2017 Free Software Foundation, Inc. |
| Contributed by Ilya Enkovich (ilya.enkovich@intel.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 |
| <http://www.gnu.org/licenses/>. */ |
| |
| #include "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| #include "backend.h" |
| #include "target.h" |
| #include "rtl.h" |
| #include "tree.h" |
| #include "gimple.h" |
| #include "cfghooks.h" |
| #include "tree-pass.h" |
| #include "ssa.h" |
| #include "cgraph.h" |
| #include "diagnostic.h" |
| #include "fold-const.h" |
| #include "stor-layout.h" |
| #include "varasm.h" |
| #include "tree-iterator.h" |
| #include "tree-cfg.h" |
| #include "langhooks.h" |
| #include "tree-ssa-address.h" |
| #include "tree-ssa-loop-niter.h" |
| #include "gimple-pretty-print.h" |
| #include "gimple-iterator.h" |
| #include "gimplify.h" |
| #include "gimplify-me.h" |
| #include "print-tree.h" |
| #include "calls.h" |
| #include "expr.h" |
| #include "tree-ssa-propagate.h" |
| #include "tree-chkp.h" |
| #include "gimple-walk.h" |
| #include "tree-dfa.h" |
| #include "ipa-chkp.h" |
| #include "params.h" |
| |
| /* Pointer Bounds Checker instruments code with memory checks to find |
| out-of-bounds memory accesses. Checks are performed by computing |
| bounds for each pointer and then comparing address of accessed |
| memory before pointer dereferencing. |
| |
| 1. Function clones. |
| |
| See ipa-chkp.c. |
| |
| 2. Instrumentation. |
| |
| There are few things to instrument: |
| |
| a) Memory accesses - add checker calls to check address of accessed memory |
| against bounds of dereferenced pointer. Obviously safe memory |
| accesses like static variable access does not have to be instrumented |
| with checks. |
| |
| Example: |
| |
| val_2 = *p_1; |
| |
| with 4 bytes access is transformed into: |
| |
| __builtin___chkp_bndcl (__bound_tmp.1_3, p_1); |
| D.1_4 = p_1 + 3; |
| __builtin___chkp_bndcu (__bound_tmp.1_3, D.1_4); |
| val_2 = *p_1; |
| |
| where __bound_tmp.1_3 are bounds computed for pointer p_1, |
| __builtin___chkp_bndcl is a lower bound check and |
| __builtin___chkp_bndcu is an upper bound check. |
| |
| b) Pointer stores. |
| |
| When pointer is stored in memory we need to store its bounds. To |
| achieve compatibility of instrumented code with regular codes |
| we have to keep data layout and store bounds in special bound tables |
| via special checker call. Implementation of bounds table may vary for |
| different platforms. It has to associate pointer value and its |
| location (it is required because we may have two equal pointers |
| with different bounds stored in different places) with bounds. |
| Another checker builtin allows to get bounds for specified pointer |
| loaded from specified location. |
| |
| Example: |
| |
| buf1[i_1] = &buf2; |
| |
| is transformed into: |
| |
| buf1[i_1] = &buf2; |
| D.1_2 = &buf1[i_1]; |
| __builtin___chkp_bndstx (D.1_2, &buf2, __bound_tmp.1_2); |
| |
| where __bound_tmp.1_2 are bounds of &buf2. |
| |
| c) Static initialization. |
| |
| The special case of pointer store is static pointer initialization. |
| Bounds initialization is performed in a few steps: |
| - register all static initializations in front-end using |
| chkp_register_var_initializer |
| - when file compilation finishes we create functions with special |
| attribute 'chkp ctor' and put explicit initialization code |
| (assignments) for all statically initialized pointers. |
| - when checker constructor is compiled checker pass adds required |
| bounds initialization for all statically initialized pointers |
| - since we do not actually need excess pointers initialization |
| in checker constructor we remove such assignments from them |
| |
| d) Calls. |
| |
| For each call in the code we add additional arguments to pass |
| bounds for pointer arguments. We determine type of call arguments |
| using arguments list from function declaration; if function |
| declaration is not available we use function type; otherwise |
| (e.g. for unnamed arguments) we use type of passed value. Function |
| declaration/type is replaced with the instrumented one. |
| |
| Example: |
| |
| val_1 = foo (&buf1, &buf2, &buf1, 0); |
| |
| is translated into: |
| |
| val_1 = foo.chkp (&buf1, __bound_tmp.1_2, &buf2, __bound_tmp.1_3, |
| &buf1, __bound_tmp.1_2, 0); |
| |
| e) Returns. |
| |
| If function returns a pointer value we have to return bounds also. |
| A new operand was added for return statement to hold returned bounds. |
| |
| Example: |
| |
| return &_buf1; |
| |
| is transformed into |
| |
| return &_buf1, __bound_tmp.1_1; |
| |
| 3. Bounds computation. |
| |
| Compiler is fully responsible for computing bounds to be used for each |
| memory access. The first step for bounds computation is to find the |
| origin of pointer dereferenced for memory access. Basing on pointer |
| origin we define a way to compute its bounds. There are just few |
| possible cases: |
| |
| a) Pointer is returned by call. |
| |
| In this case we use corresponding checker builtin method to obtain returned |
| bounds. |
| |
| Example: |
| |
| buf_1 = malloc (size_2); |
| foo (buf_1); |
| |
| is translated into: |
| |
| buf_1 = malloc (size_2); |
| __bound_tmp.1_3 = __builtin___chkp_bndret (buf_1); |
| foo (buf_1, __bound_tmp.1_3); |
| |
| b) Pointer is an address of an object. |
| |
| In this case compiler tries to compute objects size and create corresponding |
| bounds. If object has incomplete type then special checker builtin is used to |
| obtain its size at runtime. |
| |
| Example: |
| |
| foo () |
| { |
| <unnamed type> __bound_tmp.3; |
| static int buf[100]; |
| |
| <bb 3>: |
| __bound_tmp.3_2 = __builtin___chkp_bndmk (&buf, 400); |
| |
| <bb 2>: |
| return &buf, __bound_tmp.3_2; |
| } |
| |
| Example: |
| |
| Address of an object 'extern int buf[]' with incomplete type is |
| returned. |
| |
| foo () |
| { |
| <unnamed type> __bound_tmp.4; |
| long unsigned int __size_tmp.3; |
| |
| <bb 3>: |
| __size_tmp.3_4 = __builtin_ia32_sizeof (buf); |
| __bound_tmp.4_3 = __builtin_ia32_bndmk (&buf, __size_tmp.3_4); |
| |
| <bb 2>: |
| return &buf, __bound_tmp.4_3; |
| } |
| |
| c) Pointer is the result of object narrowing. |
| |
| It happens when we use pointer to an object to compute pointer to a part |
| of an object. E.g. we take pointer to a field of a structure. In this |
| case we perform bounds intersection using bounds of original object and |
| bounds of object's part (which are computed basing on its type). |
| |
| There may be some debatable questions about when narrowing should occur |
| and when it should not. To avoid false bound violations in correct |
| programs we do not perform narrowing when address of an array element is |
| obtained (it has address of the whole array) and when address of the first |
| structure field is obtained (because it is guaranteed to be equal to |
| address of the whole structure and it is legal to cast it back to structure). |
| |
| Default narrowing behavior may be changed using compiler flags. |
| |
| Example: |
| |
| In this example address of the second structure field is returned. |
| |
| foo (struct A * p, __bounds_type __bounds_of_p) |
| { |
| <unnamed type> __bound_tmp.3; |
| int * _2; |
| int * _5; |
| |
| <bb 2>: |
| _5 = &p_1(D)->second_field; |
| __bound_tmp.3_6 = __builtin___chkp_bndmk (_5, 4); |
| __bound_tmp.3_8 = __builtin___chkp_intersect (__bound_tmp.3_6, |
| __bounds_of_p_3(D)); |
| _2 = &p_1(D)->second_field; |
| return _2, __bound_tmp.3_8; |
| } |
| |
| Example: |
| |
| In this example address of the first field of array element is returned. |
| |
| foo (struct A * p, __bounds_type __bounds_of_p, int i) |
| { |
| long unsigned int _3; |
| long unsigned int _4; |
| struct A * _6; |
| int * _7; |
| |
| <bb 2>: |
| _3 = (long unsigned int) i_1(D); |
| _4 = _3 * 8; |
| _6 = p_5(D) + _4; |
| _7 = &_6->first_field; |
| return _7, __bounds_of_p_2(D); |
| } |
| |
| |
| d) Pointer is the result of pointer arithmetic or type cast. |
| |
| In this case bounds of the base pointer are used. In case of binary |
| operation producing a pointer we are analyzing data flow further |
| looking for operand's bounds. One operand is considered as a base |
| if it has some valid bounds. If we fall into a case when none of |
| operands (or both of them) has valid bounds, a default bounds value |
| is used. |
| |
| Trying to find out bounds for binary operations we may fall into |
| cyclic dependencies for pointers. To avoid infinite recursion all |
| walked phi nodes instantly obtain corresponding bounds but created |
| bounds are marked as incomplete. It helps us to stop DF walk during |
| bounds search. |
| |
| When we reach pointer source, some args of incomplete bounds phi obtain |
| valid bounds and those values are propagated further through phi nodes. |
| If no valid bounds were found for phi node then we mark its result as |
| invalid bounds. Process stops when all incomplete bounds become either |
| valid or invalid and we are able to choose a pointer base. |
| |
| e) Pointer is loaded from the memory. |
| |
| In this case we just need to load bounds from the bounds table. |
| |
| Example: |
| |
| foo () |
| { |
| <unnamed type> __bound_tmp.3; |
| static int * buf; |
| int * _2; |
| |
| <bb 2>: |
| _2 = buf; |
| __bound_tmp.3_4 = __builtin___chkp_bndldx (&buf, _2); |
| return _2, __bound_tmp.3_4; |
| } |
| |
| */ |
| |
| typedef void (*assign_handler)(tree, tree, void *); |
| |
| static tree chkp_get_zero_bounds (); |
| static tree chkp_find_bounds (tree ptr, gimple_stmt_iterator *iter); |
| static tree chkp_find_bounds_loaded (tree ptr, tree ptr_src, |
| gimple_stmt_iterator *iter); |
| static void chkp_parse_array_and_component_ref (tree node, tree *ptr, |
| tree *elt, bool *safe, |
| bool *bitfield, |
| tree *bounds, |
| gimple_stmt_iterator *iter, |
| bool innermost_bounds); |
| static void chkp_parse_bit_field_ref (tree node, location_t loc, |
| tree *offset, tree *size); |
| |
| #define chkp_bndldx_fndecl \ |
| (targetm.builtin_chkp_function (BUILT_IN_CHKP_BNDLDX)) |
| #define chkp_bndstx_fndecl \ |
| (targetm.builtin_chkp_function (BUILT_IN_CHKP_BNDSTX)) |
| #define chkp_checkl_fndecl \ |
| (targetm.builtin_chkp_function (BUILT_IN_CHKP_BNDCL)) |
| #define chkp_checku_fndecl \ |
| (targetm.builtin_chkp_function (BUILT_IN_CHKP_BNDCU)) |
| #define chkp_bndmk_fndecl \ |
| (targetm.builtin_chkp_function (BUILT_IN_CHKP_BNDMK)) |
| #define chkp_ret_bnd_fndecl \ |
| (targetm.builtin_chkp_function (BUILT_IN_CHKP_BNDRET)) |
| #define chkp_intersect_fndecl \ |
| (targetm.builtin_chkp_function (BUILT_IN_CHKP_INTERSECT)) |
| #define chkp_narrow_bounds_fndecl \ |
| (targetm.builtin_chkp_function (BUILT_IN_CHKP_NARROW)) |
| #define chkp_sizeof_fndecl \ |
| (targetm.builtin_chkp_function (BUILT_IN_CHKP_SIZEOF)) |
| #define chkp_extract_lower_fndecl \ |
| (targetm.builtin_chkp_function (BUILT_IN_CHKP_EXTRACT_LOWER)) |
| #define chkp_extract_upper_fndecl \ |
| (targetm.builtin_chkp_function (BUILT_IN_CHKP_EXTRACT_UPPER)) |
| |
| static GTY (()) tree chkp_uintptr_type; |
| |
| static GTY (()) tree chkp_zero_bounds_var; |
| static GTY (()) tree chkp_none_bounds_var; |
| |
| static GTY (()) basic_block entry_block; |
| static GTY (()) tree zero_bounds; |
| static GTY (()) tree none_bounds; |
| static GTY (()) tree incomplete_bounds; |
| static GTY (()) tree tmp_var; |
| static GTY (()) tree size_tmp_var; |
| static GTY (()) bitmap chkp_abnormal_copies; |
| |
| struct hash_set<tree> *chkp_invalid_bounds; |
| struct hash_set<tree> *chkp_completed_bounds_set; |
| struct hash_map<tree, tree> *chkp_reg_bounds; |
| struct hash_map<tree, tree> *chkp_bound_vars; |
| struct hash_map<tree, tree> *chkp_reg_addr_bounds; |
| struct hash_map<tree, tree> *chkp_incomplete_bounds_map; |
| struct hash_map<tree, tree> *chkp_bounds_map; |
| struct hash_map<tree, tree> *chkp_static_var_bounds; |
| |
| static bool in_chkp_pass; |
| |
| #define CHKP_BOUND_TMP_NAME "__bound_tmp" |
| #define CHKP_SIZE_TMP_NAME "__size_tmp" |
| #define CHKP_BOUNDS_OF_SYMBOL_PREFIX "__chkp_bounds_of_" |
| #define CHKP_STRING_BOUNDS_PREFIX "__chkp_string_bounds_" |
| #define CHKP_VAR_BOUNDS_PREFIX "__chkp_var_bounds_" |
| #define CHKP_ZERO_BOUNDS_VAR_NAME "__chkp_zero_bounds" |
| #define CHKP_NONE_BOUNDS_VAR_NAME "__chkp_none_bounds" |
| |
| /* Static checker constructors may become very large and their |
| compilation with optimization may take too much time. |
| Therefore we put a limit to number of statements in one |
| constructor. Tests with 100 000 statically initialized |
| pointers showed following compilation times on Sandy Bridge |
| server (used -O2): |
| limit 100 => ~18 sec. |
| limit 300 => ~22 sec. |
| limit 1000 => ~30 sec. |
| limit 3000 => ~49 sec. |
| limit 5000 => ~55 sec. |
| limit 10000 => ~76 sec. |
| limit 100000 => ~532 sec. */ |
| #define MAX_STMTS_IN_STATIC_CHKP_CTOR (PARAM_VALUE (PARAM_CHKP_MAX_CTOR_SIZE)) |
| |
| struct chkp_ctor_stmt_list |
| { |
| tree stmts; |
| int avail; |
| }; |
| |
| /* Return 1 if function FNDECL is instrumented by Pointer |
| Bounds Checker. */ |
| bool |
| chkp_function_instrumented_p (tree fndecl) |
| { |
| return fndecl |
| && lookup_attribute ("chkp instrumented", DECL_ATTRIBUTES (fndecl)); |
| } |
| |
| /* Mark function FNDECL as instrumented. */ |
| void |
| chkp_function_mark_instrumented (tree fndecl) |
| { |
| if (chkp_function_instrumented_p (fndecl)) |
| return; |
| |
| DECL_ATTRIBUTES (fndecl) |
| = tree_cons (get_identifier ("chkp instrumented"), NULL, |
| DECL_ATTRIBUTES (fndecl)); |
| } |
| |
| /* Return true when STMT is builtin call to instrumentation function |
| corresponding to CODE. */ |
| |
| bool |
| chkp_gimple_call_builtin_p (gimple *call, |
| enum built_in_function code) |
| { |
| tree fndecl; |
| if (gimple_call_builtin_p (call, BUILT_IN_MD) |
| && (fndecl = targetm.builtin_chkp_function (code)) |
| && (DECL_FUNCTION_CODE (gimple_call_fndecl (call)) |
| == DECL_FUNCTION_CODE (fndecl))) |
| return true; |
| return false; |
| } |
| |
| /* Emit code to build zero bounds and return RTL holding |
| the result. */ |
| rtx |
| chkp_expand_zero_bounds () |
| { |
| tree zero_bnd; |
| |
| if (flag_chkp_use_static_const_bounds) |
| zero_bnd = chkp_get_zero_bounds_var (); |
| else |
| zero_bnd = chkp_build_make_bounds_call (integer_zero_node, |
| integer_zero_node); |
| return expand_normal (zero_bnd); |
| } |
| |
| /* Emit code to store zero bounds for PTR located at MEM. */ |
| void |
| chkp_expand_bounds_reset_for_mem (tree mem, tree ptr) |
| { |
| tree zero_bnd, bnd, addr, bndstx; |
| |
| if (flag_chkp_use_static_const_bounds) |
| zero_bnd = chkp_get_zero_bounds_var (); |
| else |
| zero_bnd = chkp_build_make_bounds_call (integer_zero_node, |
| integer_zero_node); |
| bnd = make_tree (pointer_bounds_type_node, |
| assign_temp (pointer_bounds_type_node, 0, 1)); |
| addr = build1 (ADDR_EXPR, |
| build_pointer_type (TREE_TYPE (mem)), mem); |
| bndstx = chkp_build_bndstx_call (addr, ptr, bnd); |
| |
| expand_assignment (bnd, zero_bnd, false); |
| expand_normal (bndstx); |
| } |
| |
| /* Build retbnd call for returned value RETVAL. |
| |
| If BNDVAL is not NULL then result is stored |
| in it. Otherwise a temporary is created to |
| hold returned value. |
| |
| GSI points to a position for a retbnd call |
| and is set to created stmt. |
| |
| Cgraph edge is created for a new call if |
| UPDATE_EDGE is 1. |
| |
| Obtained bounds are returned. */ |
| tree |
| chkp_insert_retbnd_call (tree bndval, tree retval, |
| gimple_stmt_iterator *gsi) |
| { |
| gimple *call; |
| |
| if (!bndval) |
| bndval = create_tmp_reg (pointer_bounds_type_node, "retbnd"); |
| |
| call = gimple_build_call (chkp_ret_bnd_fndecl, 1, retval); |
| gimple_call_set_lhs (call, bndval); |
| gsi_insert_after (gsi, call, GSI_CONTINUE_LINKING); |
| |
| return bndval; |
| } |
| |
| /* Build a GIMPLE_CALL identical to CALL but skipping bounds |
| arguments. */ |
| |
| gcall * |
| chkp_copy_call_skip_bounds (gcall *call) |
| { |
| bitmap bounds; |
| unsigned i; |
| |
| bitmap_obstack_initialize (NULL); |
| bounds = BITMAP_ALLOC (NULL); |
| |
| for (i = 0; i < gimple_call_num_args (call); i++) |
| if (POINTER_BOUNDS_P (gimple_call_arg (call, i))) |
| bitmap_set_bit (bounds, i); |
| |
| if (!bitmap_empty_p (bounds)) |
| call = gimple_call_copy_skip_args (call, bounds); |
| gimple_call_set_with_bounds (call, false); |
| |
| BITMAP_FREE (bounds); |
| bitmap_obstack_release (NULL); |
| |
| return call; |
| } |
| |
| /* Redirect edge E to the correct node according to call_stmt. |
| Return 1 if bounds removal from call_stmt should be done |
| instead of redirection. */ |
| |
| bool |
| chkp_redirect_edge (cgraph_edge *e) |
| { |
| bool instrumented = false; |
| tree decl = e->callee->decl; |
| |
| if (e->callee->instrumentation_clone |
| || chkp_function_instrumented_p (decl)) |
| instrumented = true; |
| |
| if (instrumented |
| && !gimple_call_with_bounds_p (e->call_stmt)) |
| e->redirect_callee (cgraph_node::get_create (e->callee->orig_decl)); |
| else if (!instrumented |
| && gimple_call_with_bounds_p (e->call_stmt) |
| && !chkp_gimple_call_builtin_p (e->call_stmt, BUILT_IN_CHKP_BNDCL) |
| && !chkp_gimple_call_builtin_p (e->call_stmt, BUILT_IN_CHKP_BNDCU) |
| && !chkp_gimple_call_builtin_p (e->call_stmt, BUILT_IN_CHKP_BNDSTX)) |
| { |
| if (e->callee->instrumented_version) |
| e->redirect_callee (e->callee->instrumented_version); |
| else |
| { |
| tree args = TYPE_ARG_TYPES (TREE_TYPE (decl)); |
| /* Avoid bounds removal if all args will be removed. */ |
| if (!args || TREE_VALUE (args) != void_type_node) |
| return true; |
| else |
| gimple_call_set_with_bounds (e->call_stmt, false); |
| } |
| } |
| |
| return false; |
| } |
| |
| /* Mark statement S to not be instrumented. */ |
| static void |
| chkp_mark_stmt (gimple *s) |
| { |
| gimple_set_plf (s, GF_PLF_1, true); |
| } |
| |
| /* Mark statement S to be instrumented. */ |
| static void |
| chkp_unmark_stmt (gimple *s) |
| { |
| gimple_set_plf (s, GF_PLF_1, false); |
| } |
| |
| /* Return 1 if statement S should not be instrumented. */ |
| static bool |
| chkp_marked_stmt_p (gimple *s) |
| { |
| return gimple_plf (s, GF_PLF_1); |
| } |
| |
| /* Get var to be used for bound temps. */ |
| static tree |
| chkp_get_tmp_var (void) |
| { |
| if (!tmp_var) |
| tmp_var = create_tmp_reg (pointer_bounds_type_node, CHKP_BOUND_TMP_NAME); |
| |
| return tmp_var; |
| } |
| |
| /* Get SSA_NAME to be used as temp. */ |
| static tree |
| chkp_get_tmp_reg (gimple *stmt) |
| { |
| if (in_chkp_pass) |
| return make_ssa_name (chkp_get_tmp_var (), stmt); |
| |
| return make_temp_ssa_name (pointer_bounds_type_node, stmt, |
| CHKP_BOUND_TMP_NAME); |
| } |
| |
| /* Get var to be used for size temps. */ |
| static tree |
| chkp_get_size_tmp_var (void) |
| { |
| if (!size_tmp_var) |
| size_tmp_var = create_tmp_reg (chkp_uintptr_type, CHKP_SIZE_TMP_NAME); |
| |
| return size_tmp_var; |
| } |
| |
| /* Register bounds BND for address of OBJ. */ |
| static void |
| chkp_register_addr_bounds (tree obj, tree bnd) |
| { |
| if (bnd == incomplete_bounds) |
| return; |
| |
| chkp_reg_addr_bounds->put (obj, bnd); |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Regsitered bound "); |
| print_generic_expr (dump_file, bnd, 0); |
| fprintf (dump_file, " for address of "); |
| print_generic_expr (dump_file, obj, 0); |
| fprintf (dump_file, "\n"); |
| } |
| } |
| |
| /* Return bounds registered for address of OBJ. */ |
| static tree |
| chkp_get_registered_addr_bounds (tree obj) |
| { |
| tree *slot = chkp_reg_addr_bounds->get (obj); |
| return slot ? *slot : NULL_TREE; |
| } |
| |
| /* Mark BOUNDS as completed. */ |
| static void |
| chkp_mark_completed_bounds (tree bounds) |
| { |
| chkp_completed_bounds_set->add (bounds); |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Marked bounds "); |
| print_generic_expr (dump_file, bounds, 0); |
| fprintf (dump_file, " as completed\n"); |
| } |
| } |
| |
| /* Return 1 if BOUNDS were marked as completed and 0 otherwise. */ |
| static bool |
| chkp_completed_bounds (tree bounds) |
| { |
| return chkp_completed_bounds_set->contains (bounds); |
| } |
| |
| /* Clear comleted bound marks. */ |
| static void |
| chkp_erase_completed_bounds (void) |
| { |
| delete chkp_completed_bounds_set; |
| chkp_completed_bounds_set = new hash_set<tree>; |
| } |
| |
| /* Mark BOUNDS associated with PTR as incomplete. */ |
| static void |
| chkp_register_incomplete_bounds (tree bounds, tree ptr) |
| { |
| chkp_incomplete_bounds_map->put (bounds, ptr); |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Regsitered incomplete bounds "); |
| print_generic_expr (dump_file, bounds, 0); |
| fprintf (dump_file, " for "); |
| print_generic_expr (dump_file, ptr, 0); |
| fprintf (dump_file, "\n"); |
| } |
| } |
| |
| /* Return 1 if BOUNDS are incomplete and 0 otherwise. */ |
| static bool |
| chkp_incomplete_bounds (tree bounds) |
| { |
| if (bounds == incomplete_bounds) |
| return true; |
| |
| if (chkp_completed_bounds (bounds)) |
| return false; |
| |
| return chkp_incomplete_bounds_map->get (bounds) != NULL; |
| } |
| |
| /* Clear incomleted bound marks. */ |
| static void |
| chkp_erase_incomplete_bounds (void) |
| { |
| delete chkp_incomplete_bounds_map; |
| chkp_incomplete_bounds_map = new hash_map<tree, tree>; |
| } |
| |
| /* Build and return bndmk call which creates bounds for structure |
| pointed by PTR. Structure should have complete type. */ |
| tree |
| chkp_make_bounds_for_struct_addr (tree ptr) |
| { |
| tree type = TREE_TYPE (ptr); |
| tree size; |
| |
| gcc_assert (POINTER_TYPE_P (type)); |
| |
| size = TYPE_SIZE (TREE_TYPE (type)); |
| |
| gcc_assert (size); |
| |
| return build_call_nary (pointer_bounds_type_node, |
| build_fold_addr_expr (chkp_bndmk_fndecl), |
| 2, ptr, size); |
| } |
| |
| /* Traversal function for chkp_may_finish_incomplete_bounds. |
| Set RES to 0 if at least one argument of phi statement |
| defining bounds (passed in KEY arg) is unknown. |
| Traversal stops when first unknown phi argument is found. */ |
| bool |
| chkp_may_complete_phi_bounds (tree const &bounds, tree *slot ATTRIBUTE_UNUSED, |
| bool *res) |
| { |
| gimple *phi; |
| unsigned i; |
| |
| gcc_assert (TREE_CODE (bounds) == SSA_NAME); |
| |
| phi = SSA_NAME_DEF_STMT (bounds); |
| |
| gcc_assert (phi && gimple_code (phi) == GIMPLE_PHI); |
| |
| for (i = 0; i < gimple_phi_num_args (phi); i++) |
| { |
| tree phi_arg = gimple_phi_arg_def (phi, i); |
| if (!phi_arg) |
| { |
| *res = false; |
| /* Do not need to traverse further. */ |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| /* Return 1 if all phi nodes created for bounds have their |
| arguments computed. */ |
| static bool |
| chkp_may_finish_incomplete_bounds (void) |
| { |
| bool res = true; |
| |
| chkp_incomplete_bounds_map |
| ->traverse<bool *, chkp_may_complete_phi_bounds> (&res); |
| |
| return res; |
| } |
| |
| /* Helper function for chkp_finish_incomplete_bounds. |
| Recompute args for bounds phi node. */ |
| bool |
| chkp_recompute_phi_bounds (tree const &bounds, tree *slot, |
| void *res ATTRIBUTE_UNUSED) |
| { |
| tree ptr = *slot; |
| gphi *bounds_phi; |
| gphi *ptr_phi; |
| unsigned i; |
| |
| gcc_assert (TREE_CODE (bounds) == SSA_NAME); |
| gcc_assert (TREE_CODE (ptr) == SSA_NAME); |
| |
| bounds_phi = as_a <gphi *> (SSA_NAME_DEF_STMT (bounds)); |
| ptr_phi = as_a <gphi *> (SSA_NAME_DEF_STMT (ptr)); |
| |
| for (i = 0; i < gimple_phi_num_args (bounds_phi); i++) |
| { |
| tree ptr_arg = gimple_phi_arg_def (ptr_phi, i); |
| tree bound_arg = chkp_find_bounds (ptr_arg, NULL); |
| |
| add_phi_arg (bounds_phi, bound_arg, |
| gimple_phi_arg_edge (ptr_phi, i), |
| UNKNOWN_LOCATION); |
| } |
| |
| return true; |
| } |
| |
| /* Mark BOUNDS as invalid. */ |
| static void |
| chkp_mark_invalid_bounds (tree bounds) |
| { |
| chkp_invalid_bounds->add (bounds); |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Marked bounds "); |
| print_generic_expr (dump_file, bounds, 0); |
| fprintf (dump_file, " as invalid\n"); |
| } |
| } |
| |
| /* Return 1 if BOUNDS were marked as invalid and 0 otherwise. */ |
| static bool |
| chkp_valid_bounds (tree bounds) |
| { |
| if (bounds == zero_bounds || bounds == none_bounds) |
| return false; |
| |
| return !chkp_invalid_bounds->contains (bounds); |
| } |
| |
| /* Helper function for chkp_finish_incomplete_bounds. |
| Check all arguments of phi nodes trying to find |
| valid completed bounds. If there is at least one |
| such arg then bounds produced by phi node are marked |
| as valid completed bounds and all phi args are |
| recomputed. */ |
| bool |
| chkp_find_valid_phi_bounds (tree const &bounds, tree *slot, bool *res) |
| { |
| gimple *phi; |
| unsigned i; |
| |
| gcc_assert (TREE_CODE (bounds) == SSA_NAME); |
| |
| if (chkp_completed_bounds (bounds)) |
| return true; |
| |
| phi = SSA_NAME_DEF_STMT (bounds); |
| |
| gcc_assert (phi && gimple_code (phi) == GIMPLE_PHI); |
| |
| for (i = 0; i < gimple_phi_num_args (phi); i++) |
| { |
| tree phi_arg = gimple_phi_arg_def (phi, i); |
| |
| gcc_assert (phi_arg); |
| |
| if (chkp_valid_bounds (phi_arg) && !chkp_incomplete_bounds (phi_arg)) |
| { |
| *res = true; |
| chkp_mark_completed_bounds (bounds); |
| chkp_recompute_phi_bounds (bounds, slot, NULL); |
| return true; |
| } |
| } |
| |
| return true; |
| } |
| |
| /* Helper function for chkp_finish_incomplete_bounds. |
| Marks all incompleted bounds as invalid. */ |
| bool |
| chkp_mark_invalid_bounds_walker (tree const &bounds, |
| tree *slot ATTRIBUTE_UNUSED, |
| void *res ATTRIBUTE_UNUSED) |
| { |
| if (!chkp_completed_bounds (bounds)) |
| { |
| chkp_mark_invalid_bounds (bounds); |
| chkp_mark_completed_bounds (bounds); |
| } |
| return true; |
| } |
| |
| /* When all bound phi nodes have all their args computed |
| we have enough info to find valid bounds. We iterate |
| through all incompleted bounds searching for valid |
| bounds. Found valid bounds are marked as completed |
| and all remaining incompleted bounds are recomputed. |
| Process continues until no new valid bounds may be |
| found. All remained incompleted bounds are marked as |
| invalid (i.e. have no valid source of bounds). */ |
| static void |
| chkp_finish_incomplete_bounds (void) |
| { |
| bool found_valid = true; |
| |
| while (found_valid) |
| { |
| found_valid = false; |
| |
| chkp_incomplete_bounds_map-> |
| traverse<bool *, chkp_find_valid_phi_bounds> (&found_valid); |
| |
| if (found_valid) |
| chkp_incomplete_bounds_map-> |
| traverse<void *, chkp_recompute_phi_bounds> (NULL); |
| } |
| |
| chkp_incomplete_bounds_map-> |
| traverse<void *, chkp_mark_invalid_bounds_walker> (NULL); |
| chkp_incomplete_bounds_map-> |
| traverse<void *, chkp_recompute_phi_bounds> (NULL); |
| |
| chkp_erase_completed_bounds (); |
| chkp_erase_incomplete_bounds (); |
| } |
| |
| /* Return 1 if type TYPE is a pointer type or a |
| structure having a pointer type as one of its fields. |
| Otherwise return 0. */ |
| bool |
| chkp_type_has_pointer (const_tree type) |
| { |
| bool res = false; |
| |
| if (BOUNDED_TYPE_P (type)) |
| res = true; |
| else if (RECORD_OR_UNION_TYPE_P (type)) |
| { |
| tree field; |
| |
| for (field = TYPE_FIELDS (type); field; field = DECL_CHAIN (field)) |
| if (TREE_CODE (field) == FIELD_DECL) |
| res = res || chkp_type_has_pointer (TREE_TYPE (field)); |
| } |
| else if (TREE_CODE (type) == ARRAY_TYPE) |
| res = chkp_type_has_pointer (TREE_TYPE (type)); |
| |
| return res; |
| } |
| |
| unsigned |
| chkp_type_bounds_count (const_tree type) |
| { |
| unsigned res = 0; |
| |
| if (!type) |
| res = 0; |
| else if (BOUNDED_TYPE_P (type)) |
| res = 1; |
| else if (RECORD_OR_UNION_TYPE_P (type)) |
| { |
| bitmap have_bound; |
| |
| bitmap_obstack_initialize (NULL); |
| have_bound = BITMAP_ALLOC (NULL); |
| chkp_find_bound_slots (type, have_bound); |
| res = bitmap_count_bits (have_bound); |
| BITMAP_FREE (have_bound); |
| bitmap_obstack_release (NULL); |
| } |
| |
| return res; |
| } |
| |
| /* Get bounds associated with NODE via |
| chkp_set_bounds call. */ |
| tree |
| chkp_get_bounds (tree node) |
| { |
| tree *slot; |
| |
| if (!chkp_bounds_map) |
| return NULL_TREE; |
| |
| slot = chkp_bounds_map->get (node); |
| return slot ? *slot : NULL_TREE; |
| } |
| |
| /* Associate bounds VAL with NODE. */ |
| void |
| chkp_set_bounds (tree node, tree val) |
| { |
| if (!chkp_bounds_map) |
| chkp_bounds_map = new hash_map<tree, tree>; |
| |
| chkp_bounds_map->put (node, val); |
| } |
| |
| /* Check if statically initialized variable VAR require |
| static bounds initialization. If VAR is added into |
| bounds initlization list then 1 is returned. Otherwise |
| return 0. */ |
| extern bool |
| chkp_register_var_initializer (tree var) |
| { |
| if (!flag_check_pointer_bounds |
| || DECL_INITIAL (var) == error_mark_node) |
| return false; |
| |
| gcc_assert (VAR_P (var)); |
| gcc_assert (DECL_INITIAL (var)); |
| |
| if (TREE_STATIC (var) |
| && chkp_type_has_pointer (TREE_TYPE (var))) |
| { |
| varpool_node::get_create (var)->need_bounds_init = 1; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /* Helper function for chkp_finish_file. |
| |
| Add new modification statement (RHS is assigned to LHS) |
| into list of static initializer statementes (passed in ARG). |
| If statements list becomes too big, emit checker constructor |
| and start the new one. */ |
| static void |
| chkp_add_modification_to_stmt_list (tree lhs, |
| tree rhs, |
| void *arg) |
| { |
| struct chkp_ctor_stmt_list *stmts = (struct chkp_ctor_stmt_list *)arg; |
| tree modify; |
| |
| if (!useless_type_conversion_p (TREE_TYPE (lhs), TREE_TYPE (rhs))) |
| rhs = build1 (CONVERT_EXPR, TREE_TYPE (lhs), rhs); |
| |
| modify = build2 (MODIFY_EXPR, TREE_TYPE (lhs), lhs, rhs); |
| append_to_statement_list (modify, &stmts->stmts); |
| |
| stmts->avail--; |
| } |
| |
| /* Build and return ADDR_EXPR for specified object OBJ. */ |
| static tree |
| chkp_build_addr_expr (tree obj) |
| { |
| return TREE_CODE (obj) == TARGET_MEM_REF |
| ? tree_mem_ref_addr (ptr_type_node, obj) |
| : build_fold_addr_expr (obj); |
| } |
| |
| /* Helper function for chkp_finish_file. |
| Initialize bound variable BND_VAR with bounds of variable |
| VAR to statements list STMTS. If statements list becomes |
| too big, emit checker constructor and start the new one. */ |
| static void |
| chkp_output_static_bounds (tree bnd_var, tree var, |
| struct chkp_ctor_stmt_list *stmts) |
| { |
| tree lb, ub, size; |
| |
| if (TREE_CODE (var) == STRING_CST) |
| { |
| lb = build1 (CONVERT_EXPR, size_type_node, chkp_build_addr_expr (var)); |
| size = build_int_cst (size_type_node, TREE_STRING_LENGTH (var) - 1); |
| } |
| else if (DECL_SIZE (var) |
| && !chkp_variable_size_type (TREE_TYPE (var))) |
| { |
| /* Compute bounds using statically known size. */ |
| lb = build1 (CONVERT_EXPR, size_type_node, chkp_build_addr_expr (var)); |
| size = size_binop (MINUS_EXPR, DECL_SIZE_UNIT (var), size_one_node); |
| } |
| else |
| { |
| /* Compute bounds using dynamic size. */ |
| tree call; |
| |
| lb = build1 (CONVERT_EXPR, size_type_node, chkp_build_addr_expr (var)); |
| call = build1 (ADDR_EXPR, |
| build_pointer_type (TREE_TYPE (chkp_sizeof_fndecl)), |
| chkp_sizeof_fndecl); |
| size = build_call_nary (TREE_TYPE (TREE_TYPE (chkp_sizeof_fndecl)), |
| call, 1, var); |
| |
| if (flag_chkp_zero_dynamic_size_as_infinite) |
| { |
| tree max_size, cond; |
| |
| max_size = build2 (MINUS_EXPR, size_type_node, size_zero_node, lb); |
| cond = build2 (NE_EXPR, boolean_type_node, size, size_zero_node); |
| size = build3 (COND_EXPR, size_type_node, cond, size, max_size); |
| } |
| |
| size = size_binop (MINUS_EXPR, size, size_one_node); |
| } |
| |
| ub = size_binop (PLUS_EXPR, lb, size); |
| stmts->avail -= targetm.chkp_initialize_bounds (bnd_var, lb, ub, |
| &stmts->stmts); |
| if (stmts->avail <= 0) |
| { |
| cgraph_build_static_cdtor ('B', stmts->stmts, |
| MAX_RESERVED_INIT_PRIORITY + 2); |
| stmts->avail = MAX_STMTS_IN_STATIC_CHKP_CTOR; |
| stmts->stmts = NULL; |
| } |
| } |
| |
| /* Return entry block to be used for checker initilization code. |
| Create new block if required. */ |
| static basic_block |
| chkp_get_entry_block (void) |
| { |
| if (!entry_block) |
| entry_block |
| = split_block_after_labels (ENTRY_BLOCK_PTR_FOR_FN (cfun))->dest; |
| |
| return entry_block; |
| } |
| |
| /* Return a bounds var to be used for pointer var PTR_VAR. */ |
| static tree |
| chkp_get_bounds_var (tree ptr_var) |
| { |
| tree bnd_var; |
| tree *slot; |
| |
| slot = chkp_bound_vars->get (ptr_var); |
| if (slot) |
| bnd_var = *slot; |
| else |
| { |
| bnd_var = create_tmp_reg (pointer_bounds_type_node, |
| CHKP_BOUND_TMP_NAME); |
| chkp_bound_vars->put (ptr_var, bnd_var); |
| } |
| |
| return bnd_var; |
| } |
| |
| /* If BND is an abnormal bounds copy, return a copied value. |
| Otherwise return BND. */ |
| static tree |
| chkp_get_orginal_bounds_for_abnormal_copy (tree bnd) |
| { |
| if (bitmap_bit_p (chkp_abnormal_copies, SSA_NAME_VERSION (bnd))) |
| { |
| gimple *bnd_def = SSA_NAME_DEF_STMT (bnd); |
| gcc_checking_assert (gimple_code (bnd_def) == GIMPLE_ASSIGN); |
| bnd = gimple_assign_rhs1 (bnd_def); |
| } |
| |
| return bnd; |
| } |
| |
| /* Register bounds BND for object PTR in global bounds table. |
| A copy of bounds may be created for abnormal ssa names. |
| Returns bounds to use for PTR. */ |
| static tree |
| chkp_maybe_copy_and_register_bounds (tree ptr, tree bnd) |
| { |
| bool abnormal_ptr; |
| |
| if (!chkp_reg_bounds) |
| return bnd; |
| |
| /* Do nothing if bounds are incomplete_bounds |
| because it means bounds will be recomputed. */ |
| if (bnd == incomplete_bounds) |
| return bnd; |
| |
| abnormal_ptr = (TREE_CODE (ptr) == SSA_NAME |
| && SSA_NAME_OCCURS_IN_ABNORMAL_PHI (ptr) |
| && gimple_code (SSA_NAME_DEF_STMT (ptr)) != GIMPLE_PHI); |
| |
| /* A single bounds value may be reused multiple times for |
| different pointer values. It may cause coalescing issues |
| for abnormal SSA names. To avoid it we create a bounds |
| copy in case it is computed for abnormal SSA name. |
| |
| We also cannot reuse such created copies for other pointers */ |
| if (abnormal_ptr |
| || bitmap_bit_p (chkp_abnormal_copies, SSA_NAME_VERSION (bnd))) |
| { |
| tree bnd_var = NULL_TREE; |
| |
| if (abnormal_ptr) |
| { |
| if (SSA_NAME_VAR (ptr)) |
| bnd_var = chkp_get_bounds_var (SSA_NAME_VAR (ptr)); |
| } |
| else |
| bnd_var = chkp_get_tmp_var (); |
| |
| /* For abnormal copies we may just find original |
| bounds and use them. */ |
| if (!abnormal_ptr && !SSA_NAME_IS_DEFAULT_DEF (bnd)) |
| bnd = chkp_get_orginal_bounds_for_abnormal_copy (bnd); |
| /* For undefined values we usually use none bounds |
| value but in case of abnormal edge it may cause |
| coalescing failures. Use default definition of |
| bounds variable instead to avoid it. */ |
| else if (SSA_NAME_IS_DEFAULT_DEF (ptr) |
| && TREE_CODE (SSA_NAME_VAR (ptr)) != PARM_DECL) |
| { |
| bnd = get_or_create_ssa_default_def (cfun, bnd_var); |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Using default def bounds "); |
| print_generic_expr (dump_file, bnd, 0); |
| fprintf (dump_file, " for abnormal default def SSA name "); |
| print_generic_expr (dump_file, ptr, 0); |
| fprintf (dump_file, "\n"); |
| } |
| } |
| else |
| { |
| tree copy; |
| gimple *def = SSA_NAME_DEF_STMT (ptr); |
| gimple *assign; |
| gimple_stmt_iterator gsi; |
| |
| if (bnd_var) |
| copy = make_ssa_name (bnd_var); |
| else |
| copy = make_temp_ssa_name (pointer_bounds_type_node, |
| NULL, |
| CHKP_BOUND_TMP_NAME); |
| bnd = chkp_get_orginal_bounds_for_abnormal_copy (bnd); |
| assign = gimple_build_assign (copy, bnd); |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Creating a copy of bounds "); |
| print_generic_expr (dump_file, bnd, 0); |
| fprintf (dump_file, " for abnormal SSA name "); |
| print_generic_expr (dump_file, ptr, 0); |
| fprintf (dump_file, "\n"); |
| } |
| |
| if (gimple_code (def) == GIMPLE_NOP) |
| { |
| gsi = gsi_last_bb (chkp_get_entry_block ()); |
| if (!gsi_end_p (gsi) && is_ctrl_stmt (gsi_stmt (gsi))) |
| gsi_insert_before (&gsi, assign, GSI_CONTINUE_LINKING); |
| else |
| gsi_insert_after (&gsi, assign, GSI_CONTINUE_LINKING); |
| } |
| else |
| { |
| gimple *bnd_def = SSA_NAME_DEF_STMT (bnd); |
| /* Sometimes (e.g. when we load a pointer from a |
| memory) bounds are produced later than a pointer. |
| We need to insert bounds copy appropriately. */ |
| if (gimple_code (bnd_def) != GIMPLE_NOP |
| && stmt_dominates_stmt_p (def, bnd_def)) |
| gsi = gsi_for_stmt (bnd_def); |
| else |
| gsi = gsi_for_stmt (def); |
| gsi_insert_after (&gsi, assign, GSI_CONTINUE_LINKING); |
| } |
| |
| bnd = copy; |
| } |
| |
| if (abnormal_ptr) |
| bitmap_set_bit (chkp_abnormal_copies, SSA_NAME_VERSION (bnd)); |
| } |
| |
| chkp_reg_bounds->put (ptr, bnd); |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Regsitered bound "); |
| print_generic_expr (dump_file, bnd, 0); |
| fprintf (dump_file, " for pointer "); |
| print_generic_expr (dump_file, ptr, 0); |
| fprintf (dump_file, "\n"); |
| } |
| |
| return bnd; |
| } |
| |
| /* Get bounds registered for object PTR in global bounds table. */ |
| static tree |
| chkp_get_registered_bounds (tree ptr) |
| { |
| tree *slot; |
| |
| if (!chkp_reg_bounds) |
| return NULL_TREE; |
| |
| slot = chkp_reg_bounds->get (ptr); |
| return slot ? *slot : NULL_TREE; |
| } |
| |
| /* Add bound retvals to return statement pointed by GSI. */ |
| |
| static void |
| chkp_add_bounds_to_ret_stmt (gimple_stmt_iterator *gsi) |
| { |
| greturn *ret = as_a <greturn *> (gsi_stmt (*gsi)); |
| tree retval = gimple_return_retval (ret); |
| tree ret_decl = DECL_RESULT (cfun->decl); |
| tree bounds; |
| |
| if (!retval) |
| return; |
| |
| if (BOUNDED_P (ret_decl)) |
| { |
| bounds = chkp_find_bounds (retval, gsi); |
| bounds = chkp_maybe_copy_and_register_bounds (ret_decl, bounds); |
| gimple_return_set_retbnd (ret, bounds); |
| } |
| |
| update_stmt (ret); |
| } |
| |
| /* Force OP to be suitable for using as an argument for call. |
| New statements (if any) go to SEQ. */ |
| static tree |
| chkp_force_gimple_call_op (tree op, gimple_seq *seq) |
| { |
| gimple_seq stmts; |
| gimple_stmt_iterator si; |
| |
| op = force_gimple_operand (unshare_expr (op), &stmts, true, NULL_TREE); |
| |
| for (si = gsi_start (stmts); !gsi_end_p (si); gsi_next (&si)) |
| chkp_mark_stmt (gsi_stmt (si)); |
| |
| gimple_seq_add_seq (seq, stmts); |
| |
| return op; |
| } |
| |
| /* Generate lower bound check for memory access by ADDR. |
| Check is inserted before the position pointed by ITER. |
| DIRFLAG indicates whether memory access is load or store. */ |
| static void |
| chkp_check_lower (tree addr, tree bounds, |
| gimple_stmt_iterator iter, |
| location_t location, |
| tree dirflag) |
| { |
| gimple_seq seq; |
| gimple *check; |
| tree node; |
| |
| if (!chkp_function_instrumented_p (current_function_decl) |
| && bounds == chkp_get_zero_bounds ()) |
| return; |
| |
| if (dirflag == integer_zero_node |
| && !flag_chkp_check_read) |
| return; |
| |
| if (dirflag == integer_one_node |
| && !flag_chkp_check_write) |
| return; |
| |
| seq = NULL; |
| |
| node = chkp_force_gimple_call_op (addr, &seq); |
| |
| check = gimple_build_call (chkp_checkl_fndecl, 2, node, bounds); |
| chkp_mark_stmt (check); |
| gimple_call_set_with_bounds (check, true); |
| gimple_set_location (check, location); |
| gimple_seq_add_stmt (&seq, check); |
| |
| gsi_insert_seq_before (&iter, seq, GSI_SAME_STMT); |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| gimple *before = gsi_stmt (iter); |
| fprintf (dump_file, "Generated lower bound check for statement "); |
| print_gimple_stmt (dump_file, before, 0, TDF_VOPS|TDF_MEMSYMS); |
| fprintf (dump_file, " "); |
| print_gimple_stmt (dump_file, check, 0, TDF_VOPS|TDF_MEMSYMS); |
| } |
| } |
| |
| /* Generate upper bound check for memory access by ADDR. |
| Check is inserted before the position pointed by ITER. |
| DIRFLAG indicates whether memory access is load or store. */ |
| static void |
| chkp_check_upper (tree addr, tree bounds, |
| gimple_stmt_iterator iter, |
| location_t location, |
| tree dirflag) |
| { |
| gimple_seq seq; |
| gimple *check; |
| tree node; |
| |
| if (!chkp_function_instrumented_p (current_function_decl) |
| && bounds == chkp_get_zero_bounds ()) |
| return; |
| |
| if (dirflag == integer_zero_node |
| && !flag_chkp_check_read) |
| return; |
| |
| if (dirflag == integer_one_node |
| && !flag_chkp_check_write) |
| return; |
| |
| seq = NULL; |
| |
| node = chkp_force_gimple_call_op (addr, &seq); |
| |
| check = gimple_build_call (chkp_checku_fndecl, 2, node, bounds); |
| chkp_mark_stmt (check); |
| gimple_call_set_with_bounds (check, true); |
| gimple_set_location (check, location); |
| gimple_seq_add_stmt (&seq, check); |
| |
| gsi_insert_seq_before (&iter, seq, GSI_SAME_STMT); |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| gimple *before = gsi_stmt (iter); |
| fprintf (dump_file, "Generated upper bound check for statement "); |
| print_gimple_stmt (dump_file, before, 0, TDF_VOPS|TDF_MEMSYMS); |
| fprintf (dump_file, " "); |
| print_gimple_stmt (dump_file, check, 0, TDF_VOPS|TDF_MEMSYMS); |
| } |
| } |
| |
| /* Generate lower and upper bound checks for memory access |
| to memory slot [FIRST, LAST] againsr BOUNDS. Checks |
| are inserted before the position pointed by ITER. |
| DIRFLAG indicates whether memory access is load or store. */ |
| void |
| chkp_check_mem_access (tree first, tree last, tree bounds, |
| gimple_stmt_iterator iter, |
| location_t location, |
| tree dirflag) |
| { |
| chkp_check_lower (first, bounds, iter, location, dirflag); |
| chkp_check_upper (last, bounds, iter, location, dirflag); |
| } |
| |
| /* Replace call to _bnd_chk_* pointed by GSI with |
| bndcu and bndcl calls. DIRFLAG determines whether |
| check is for read or write. */ |
| |
| void |
| chkp_replace_address_check_builtin (gimple_stmt_iterator *gsi, |
| tree dirflag) |
| { |
| gimple_stmt_iterator call_iter = *gsi; |
| gimple *call = gsi_stmt (*gsi); |
| tree fndecl = gimple_call_fndecl (call); |
| tree addr = gimple_call_arg (call, 0); |
| tree bounds = chkp_find_bounds (addr, gsi); |
| |
| if (DECL_FUNCTION_CODE (fndecl) == BUILT_IN_CHKP_CHECK_PTR_LBOUNDS |
| || DECL_FUNCTION_CODE (fndecl) == BUILT_IN_CHKP_CHECK_PTR_BOUNDS) |
| chkp_check_lower (addr, bounds, *gsi, gimple_location (call), dirflag); |
| |
| if (DECL_FUNCTION_CODE (fndecl) == BUILT_IN_CHKP_CHECK_PTR_UBOUNDS) |
| chkp_check_upper (addr, bounds, *gsi, gimple_location (call), dirflag); |
| |
| if (DECL_FUNCTION_CODE (fndecl) == BUILT_IN_CHKP_CHECK_PTR_BOUNDS) |
| { |
| tree size = gimple_call_arg (call, 1); |
| addr = fold_build_pointer_plus (addr, size); |
| addr = fold_build_pointer_plus_hwi (addr, -1); |
| chkp_check_upper (addr, bounds, *gsi, gimple_location (call), dirflag); |
| } |
| |
| gsi_remove (&call_iter, true); |
| } |
| |
| /* Replace call to _bnd_get_ptr_* pointed by GSI with |
| corresponding bounds extract call. */ |
| |
| void |
| chkp_replace_extract_builtin (gimple_stmt_iterator *gsi) |
| { |
| gimple *call = gsi_stmt (*gsi); |
| tree fndecl = gimple_call_fndecl (call); |
| tree addr = gimple_call_arg (call, 0); |
| tree bounds = chkp_find_bounds (addr, gsi); |
| gimple *extract; |
| |
| if (DECL_FUNCTION_CODE (fndecl) == BUILT_IN_CHKP_GET_PTR_LBOUND) |
| fndecl = chkp_extract_lower_fndecl; |
| else if (DECL_FUNCTION_CODE (fndecl) == BUILT_IN_CHKP_GET_PTR_UBOUND) |
| fndecl = chkp_extract_upper_fndecl; |
| else |
| gcc_unreachable (); |
| |
| extract = gimple_build_call (fndecl, 1, bounds); |
| gimple_call_set_lhs (extract, gimple_call_lhs (call)); |
| chkp_mark_stmt (extract); |
| |
| gsi_replace (gsi, extract, false); |
| } |
| |
| /* Return COMPONENT_REF accessing FIELD in OBJ. */ |
| static tree |
| chkp_build_component_ref (tree obj, tree field) |
| { |
| tree res; |
| |
| /* If object is TMR then we do not use component_ref but |
| add offset instead. We need it to be able to get addr |
| of the reasult later. */ |
| if (TREE_CODE (obj) == TARGET_MEM_REF) |
| { |
| tree offs = TMR_OFFSET (obj); |
| offs = fold_binary_to_constant (PLUS_EXPR, TREE_TYPE (offs), |
| offs, DECL_FIELD_OFFSET (field)); |
| |
| gcc_assert (offs); |
| |
| res = copy_node (obj); |
| TREE_TYPE (res) = TREE_TYPE (field); |
| TMR_OFFSET (res) = offs; |
| } |
| else |
| res = build3 (COMPONENT_REF, TREE_TYPE (field), obj, field, NULL_TREE); |
| |
| return res; |
| } |
| |
| /* Return ARRAY_REF for array ARR and index IDX with |
| specified element type ETYPE and element size ESIZE. */ |
| static tree |
| chkp_build_array_ref (tree arr, tree etype, tree esize, |
| unsigned HOST_WIDE_INT idx) |
| { |
| tree index = build_int_cst (size_type_node, idx); |
| tree res; |
| |
| /* If object is TMR then we do not use array_ref but |
| add offset instead. We need it to be able to get addr |
| of the reasult later. */ |
| if (TREE_CODE (arr) == TARGET_MEM_REF) |
| { |
| tree offs = TMR_OFFSET (arr); |
| |
| esize = fold_binary_to_constant (MULT_EXPR, TREE_TYPE (esize), |
| esize, index); |
| gcc_assert(esize); |
| |
| offs = fold_binary_to_constant (PLUS_EXPR, TREE_TYPE (offs), |
| offs, esize); |
| gcc_assert (offs); |
| |
| res = copy_node (arr); |
| TREE_TYPE (res) = etype; |
| TMR_OFFSET (res) = offs; |
| } |
| else |
| res = build4 (ARRAY_REF, etype, arr, index, NULL_TREE, NULL_TREE); |
| |
| return res; |
| } |
| |
| /* Helper function for chkp_add_bounds_to_call_stmt. |
| Fill ALL_BOUNDS output array with created bounds. |
| |
| OFFS is used for recursive calls and holds basic |
| offset of TYPE in outer structure in bits. |
| |
| ITER points a position where bounds are searched. |
| |
| ALL_BOUNDS[i] is filled with elem bounds if there |
| is a field in TYPE which has pointer type and offset |
| equal to i * POINTER_SIZE in bits. */ |
| static void |
| chkp_find_bounds_for_elem (tree elem, tree *all_bounds, |
| HOST_WIDE_INT offs, |
| gimple_stmt_iterator *iter) |
| { |
| tree type = TREE_TYPE (elem); |
| |
| if (BOUNDED_TYPE_P (type)) |
| { |
| if (!all_bounds[offs / POINTER_SIZE]) |
| { |
| tree temp = make_temp_ssa_name (type, NULL, ""); |
| gimple *assign = gimple_build_assign (temp, elem); |
| gimple_stmt_iterator gsi; |
| |
| gsi_insert_before (iter, assign, GSI_SAME_STMT); |
| gsi = gsi_for_stmt (assign); |
| |
| all_bounds[offs / POINTER_SIZE] = chkp_find_bounds (temp, &gsi); |
| } |
| } |
| else if (RECORD_OR_UNION_TYPE_P (type)) |
| { |
| tree field; |
| |
| for (field = TYPE_FIELDS (type); field; field = DECL_CHAIN (field)) |
| if (TREE_CODE (field) == FIELD_DECL) |
| { |
| tree base = unshare_expr (elem); |
| tree field_ref = chkp_build_component_ref (base, field); |
| HOST_WIDE_INT field_offs |
| = TREE_INT_CST_LOW (DECL_FIELD_BIT_OFFSET (field)); |
| if (DECL_FIELD_OFFSET (field)) |
| field_offs += TREE_INT_CST_LOW (DECL_FIELD_OFFSET (field)) * 8; |
| |
| chkp_find_bounds_for_elem (field_ref, all_bounds, |
| offs + field_offs, iter); |
| } |
| } |
| else if (TREE_CODE (type) == ARRAY_TYPE) |
| { |
| tree maxval = TYPE_MAX_VALUE (TYPE_DOMAIN (type)); |
| tree etype = TREE_TYPE (type); |
| HOST_WIDE_INT esize = TREE_INT_CST_LOW (TYPE_SIZE (etype)); |
| unsigned HOST_WIDE_INT cur; |
| |
| if (!maxval || integer_minus_onep (maxval)) |
| return; |
| |
| for (cur = 0; cur <= TREE_INT_CST_LOW (maxval); cur++) |
| { |
| tree base = unshare_expr (elem); |
| tree arr_elem = chkp_build_array_ref (base, etype, |
| TYPE_SIZE (etype), |
| cur); |
| chkp_find_bounds_for_elem (arr_elem, all_bounds, offs + cur * esize, |
| iter); |
| } |
| } |
| } |
| |
| /* Fill HAVE_BOUND output bitmap with information about |
| bounds requred for object of type TYPE. |
| |
| OFFS is used for recursive calls and holds basic |
| offset of TYPE in outer structure in bits. |
| |
| HAVE_BOUND[i] is set to 1 if there is a field |
| in TYPE which has pointer type and offset |
| equal to i * POINTER_SIZE - OFFS in bits. */ |
| void |
| chkp_find_bound_slots_1 (const_tree type, bitmap have_bound, |
| HOST_WIDE_INT offs) |
| { |
| if (BOUNDED_TYPE_P (type)) |
| bitmap_set_bit (have_bound, offs / POINTER_SIZE); |
| else if (RECORD_OR_UNION_TYPE_P (type)) |
| { |
| tree field; |
| |
| for (field = TYPE_FIELDS (type); field; field = DECL_CHAIN (field)) |
| if (TREE_CODE (field) == FIELD_DECL) |
| { |
| HOST_WIDE_INT field_offs = 0; |
| if (DECL_FIELD_BIT_OFFSET (field)) |
| field_offs += TREE_INT_CST_LOW (DECL_FIELD_BIT_OFFSET (field)); |
| if (DECL_FIELD_OFFSET (field)) |
| field_offs += TREE_INT_CST_LOW (DECL_FIELD_OFFSET (field)) * 8; |
| chkp_find_bound_slots_1 (TREE_TYPE (field), have_bound, |
| offs + field_offs); |
| } |
| } |
| else if (TREE_CODE (type) == ARRAY_TYPE && TYPE_DOMAIN (type)) |
| { |
| /* The object type is an array of complete type, i.e., other |
| than a flexible array. */ |
| tree maxval = TYPE_MAX_VALUE (TYPE_DOMAIN (type)); |
| tree etype = TREE_TYPE (type); |
| HOST_WIDE_INT esize = TREE_INT_CST_LOW (TYPE_SIZE (etype)); |
| unsigned HOST_WIDE_INT cur; |
| |
| if (!maxval |
| || TREE_CODE (maxval) != INTEGER_CST |
| || integer_minus_onep (maxval)) |
| return; |
| |
| for (cur = 0; cur <= TREE_INT_CST_LOW (maxval); cur++) |
| chkp_find_bound_slots_1 (etype, have_bound, offs + cur * esize); |
| } |
| } |
| |
| /* Fill bitmap RES with information about bounds for |
| type TYPE. See chkp_find_bound_slots_1 for more |
| details. */ |
| void |
| chkp_find_bound_slots (const_tree type, bitmap res) |
| { |
| bitmap_clear (res); |
| chkp_find_bound_slots_1 (type, res, 0); |
| } |
| |
| /* Return 1 if call to FNDECL should be instrumented |
| and 0 otherwise. */ |
| |
| static bool |
| chkp_instrument_normal_builtin (tree fndecl) |
| { |
| switch (DECL_FUNCTION_CODE (fndecl)) |
| { |
| case BUILT_IN_STRLEN: |
| case BUILT_IN_STRCPY: |
| case BUILT_IN_STRNCPY: |
| case BUILT_IN_STPCPY: |
| case BUILT_IN_STPNCPY: |
| case BUILT_IN_STRCAT: |
| case BUILT_IN_STRNCAT: |
| case BUILT_IN_MEMCPY: |
| case BUILT_IN_MEMPCPY: |
| case BUILT_IN_MEMSET: |
| case BUILT_IN_MEMMOVE: |
| case BUILT_IN_BZERO: |
| case BUILT_IN_STRCMP: |
| case BUILT_IN_STRNCMP: |
| case BUILT_IN_BCMP: |
| case BUILT_IN_MEMCMP: |
| case BUILT_IN_MEMCPY_CHK: |
| case BUILT_IN_MEMPCPY_CHK: |
| case BUILT_IN_MEMMOVE_CHK: |
| case BUILT_IN_MEMSET_CHK: |
| case BUILT_IN_STRCPY_CHK: |
| case BUILT_IN_STRNCPY_CHK: |
| case BUILT_IN_STPCPY_CHK: |
| case BUILT_IN_STPNCPY_CHK: |
| case BUILT_IN_STRCAT_CHK: |
| case BUILT_IN_STRNCAT_CHK: |
| case BUILT_IN_MALLOC: |
| case BUILT_IN_CALLOC: |
| case BUILT_IN_REALLOC: |
| return 1; |
| |
| default: |
| return 0; |
| } |
| } |
| |
| /* Add bound arguments to call statement pointed by GSI. |
| Also performs a replacement of user checker builtins calls |
| with internal ones. */ |
| |
| static void |
| chkp_add_bounds_to_call_stmt (gimple_stmt_iterator *gsi) |
| { |
| gcall *call = as_a <gcall *> (gsi_stmt (*gsi)); |
| unsigned arg_no = 0; |
| tree fndecl = gimple_call_fndecl (call); |
| tree fntype; |
| tree first_formal_arg; |
| tree arg; |
| bool use_fntype = false; |
| tree op; |
| ssa_op_iter iter; |
| gcall *new_call; |
| |
| /* Do nothing for internal functions. */ |
| if (gimple_call_internal_p (call)) |
| return; |
| |
| fntype = TREE_TYPE (TREE_TYPE (gimple_call_fn (call))); |
| |
| /* Do nothing if back-end builtin is called. */ |
| if (fndecl && DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_MD) |
| return; |
| |
| /* Do nothing for some middle-end builtins. */ |
| if (fndecl && DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_NORMAL |
| && DECL_FUNCTION_CODE (fndecl) == BUILT_IN_OBJECT_SIZE) |
| return; |
| |
| /* Do nothing for calls to not instrumentable functions. */ |
| if (fndecl && !chkp_instrumentable_p (fndecl)) |
| return; |
| |
| /* Ignore CHKP_INIT_PTR_BOUNDS, CHKP_NULL_PTR_BOUNDS |
| and CHKP_COPY_PTR_BOUNDS. */ |
| if (fndecl && DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_NORMAL |
| && (DECL_FUNCTION_CODE (fndecl) == BUILT_IN_CHKP_INIT_PTR_BOUNDS |
| || DECL_FUNCTION_CODE (fndecl) == BUILT_IN_CHKP_NULL_PTR_BOUNDS |
| || DECL_FUNCTION_CODE (fndecl) == BUILT_IN_CHKP_COPY_PTR_BOUNDS |
| || DECL_FUNCTION_CODE (fndecl) == BUILT_IN_CHKP_SET_PTR_BOUNDS)) |
| return; |
| |
| /* Check user builtins are replaced with checks. */ |
| if (fndecl && DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_NORMAL |
| && (DECL_FUNCTION_CODE (fndecl) == BUILT_IN_CHKP_CHECK_PTR_LBOUNDS |
| || DECL_FUNCTION_CODE (fndecl) == BUILT_IN_CHKP_CHECK_PTR_UBOUNDS |
| || DECL_FUNCTION_CODE (fndecl) == BUILT_IN_CHKP_CHECK_PTR_BOUNDS)) |
| { |
| chkp_replace_address_check_builtin (gsi, integer_minus_one_node); |
| return; |
| } |
| |
| /* Check user builtins are replaced with bound extract. */ |
| if (fndecl && DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_NORMAL |
| && (DECL_FUNCTION_CODE (fndecl) == BUILT_IN_CHKP_GET_PTR_LBOUND |
| || DECL_FUNCTION_CODE (fndecl) == BUILT_IN_CHKP_GET_PTR_UBOUND)) |
| { |
| chkp_replace_extract_builtin (gsi); |
| return; |
| } |
| |
| /* BUILT_IN_CHKP_NARROW_PTR_BOUNDS call is replaced with |
| target narrow bounds call. */ |
| if (fndecl && DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_NORMAL |
| && DECL_FUNCTION_CODE (fndecl) == BUILT_IN_CHKP_NARROW_PTR_BOUNDS) |
| { |
| tree arg = gimple_call_arg (call, 1); |
| tree bounds = chkp_find_bounds (arg, gsi); |
| |
| gimple_call_set_fndecl (call, chkp_narrow_bounds_fndecl); |
| gimple_call_set_arg (call, 1, bounds); |
| update_stmt (call); |
| |
| return; |
| } |
| |
| /* BUILT_IN_CHKP_STORE_PTR_BOUNDS call is replaced with |
| bndstx call. */ |
| if (fndecl && DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_NORMAL |
| && DECL_FUNCTION_CODE (fndecl) == BUILT_IN_CHKP_STORE_PTR_BOUNDS) |
| { |
| tree addr = gimple_call_arg (call, 0); |
| tree ptr = gimple_call_arg (call, 1); |
| tree bounds = chkp_find_bounds (ptr, gsi); |
| gimple_stmt_iterator iter = gsi_for_stmt (call); |
| |
| chkp_build_bndstx (addr, ptr, bounds, gsi); |
| gsi_remove (&iter, true); |
| |
| return; |
| } |
| |
| if (!flag_chkp_instrument_calls) |
| return; |
| |
| /* We instrument only some subset of builtins. We also instrument |
| builtin calls to be inlined. */ |
| if (fndecl |
| && DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_NORMAL |
| && !chkp_instrument_normal_builtin (fndecl)) |
| { |
| if (!lookup_attribute ("always_inline", DECL_ATTRIBUTES (fndecl))) |
| return; |
| |
| struct cgraph_node *clone = chkp_maybe_create_clone (fndecl); |
| if (!clone |
| || !gimple_has_body_p (clone->decl)) |
| return; |
| } |
| |
| /* If function decl is available then use it for |
| formal arguments list. Otherwise use function type. */ |
| if (fndecl |
| && DECL_ARGUMENTS (fndecl) |
| && gimple_call_fntype (call) == TREE_TYPE (fndecl)) |
| first_formal_arg = DECL_ARGUMENTS (fndecl); |
| else |
| { |
| first_formal_arg = TYPE_ARG_TYPES (fntype); |
| use_fntype = true; |
| } |
| |
| /* Fill vector of new call args. */ |
| vec<tree> new_args = vNULL; |
| new_args.create (gimple_call_num_args (call)); |
| arg = first_formal_arg; |
| for (arg_no = 0; arg_no < gimple_call_num_args (call); arg_no++) |
| { |
| tree call_arg = gimple_call_arg (call, arg_no); |
| tree type; |
| |
| /* Get arg type using formal argument description |
| or actual argument type. */ |
| if (arg) |
| if (use_fntype) |
| if (TREE_VALUE (arg) != void_type_node) |
| { |
| type = TREE_VALUE (arg); |
| arg = TREE_CHAIN (arg); |
| } |
| else |
| type = TREE_TYPE (call_arg); |
| else |
| { |
| type = TREE_TYPE (arg); |
| arg = TREE_CHAIN (arg); |
| } |
| else |
| type = TREE_TYPE (call_arg); |
| |
| new_args.safe_push (call_arg); |
| |
| if (BOUNDED_TYPE_P (type) |
| || pass_by_reference (NULL, TYPE_MODE (type), type, true)) |
| new_args.safe_push (chkp_find_bounds (call_arg, gsi)); |
| else if (chkp_type_has_pointer (type)) |
| { |
| HOST_WIDE_INT max_bounds |
| = TREE_INT_CST_LOW (TYPE_SIZE (type)) / POINTER_SIZE; |
| tree *all_bounds = (tree *)xmalloc (sizeof (tree) * max_bounds); |
| HOST_WIDE_INT bnd_no; |
| |
| memset (all_bounds, 0, sizeof (tree) * max_bounds); |
| |
| chkp_find_bounds_for_elem (call_arg, all_bounds, 0, gsi); |
| |
| for (bnd_no = 0; bnd_no < max_bounds; bnd_no++) |
| if (all_bounds[bnd_no]) |
| new_args.safe_push (all_bounds[bnd_no]); |
| |
| free (all_bounds); |
| } |
| } |
| |
| if (new_args.length () == gimple_call_num_args (call)) |
| new_call = call; |
| else |
| { |
| new_call = gimple_build_call_vec (gimple_op (call, 1), new_args); |
| gimple_call_set_lhs (new_call, gimple_call_lhs (call)); |
| gimple_call_copy_flags (new_call, call); |
| gimple_call_set_chain (new_call, gimple_call_chain (call)); |
| } |
| new_args.release (); |
| |
| /* For direct calls fndecl is replaced with instrumented version. */ |
| if (fndecl) |
| { |
| tree new_decl = chkp_maybe_create_clone (fndecl)->decl; |
| gimple_call_set_fndecl (new_call, new_decl); |
| /* In case of a type cast we should modify used function |
| type instead of using type of new fndecl. */ |
| if (gimple_call_fntype (call) != TREE_TYPE (fndecl)) |
| { |
| tree type = gimple_call_fntype (call); |
| type = chkp_copy_function_type_adding_bounds (type); |
| gimple_call_set_fntype (new_call, type); |
| } |
| else |
| gimple_call_set_fntype (new_call, TREE_TYPE (new_decl)); |
| } |
| /* For indirect call we should fix function pointer type if |
| pass some bounds. */ |
| else if (new_call != call) |
| { |
| tree type = gimple_call_fntype (call); |
| type = chkp_copy_function_type_adding_bounds (type); |
| gimple_call_set_fntype (new_call, type); |
| } |
| |
| /* replace old call statement with the new one. */ |
| if (call != new_call) |
| { |
| FOR_EACH_SSA_TREE_OPERAND (op, call, iter, SSA_OP_ALL_DEFS) |
| { |
| SSA_NAME_DEF_STMT (op) = new_call; |
| } |
| gsi_replace (gsi, new_call, true); |
| } |
| else |
| update_stmt (new_call); |
| |
| gimple_call_set_with_bounds (new_call, true); |
| } |
| |
| /* Return constant static bounds var with specified bounds LB and UB. |
| If such var does not exists then new var is created with specified NAME. */ |
| static tree |
| chkp_make_static_const_bounds (HOST_WIDE_INT lb, |
| HOST_WIDE_INT ub, |
| const char *name) |
| { |
| tree id = get_identifier (name); |
| tree var; |
| varpool_node *node; |
| symtab_node *snode; |
| |
| var = build_decl (UNKNOWN_LOCATION, VAR_DECL, id, |
| pointer_bounds_type_node); |
| TREE_STATIC (var) = 1; |
| TREE_PUBLIC (var) = 1; |
| |
| /* With LTO we may have constant bounds already in varpool. |
| Try to find it. */ |
| if ((snode = symtab_node::get_for_asmname (DECL_ASSEMBLER_NAME (var)))) |
| { |
| /* We don't allow this symbol usage for non bounds. */ |
| if (snode->type != SYMTAB_VARIABLE |
| || !POINTER_BOUNDS_P (snode->decl)) |
| sorry ("-fcheck-pointer-bounds requires %qs " |
| "name for internal usage", |
| IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (var))); |
| |
| return snode->decl; |
| } |
| |
| TREE_USED (var) = 1; |
| TREE_READONLY (var) = 1; |
| TREE_ADDRESSABLE (var) = 0; |
| DECL_ARTIFICIAL (var) = 1; |
| DECL_READ_P (var) = 1; |
| DECL_INITIAL (var) = targetm.chkp_make_bounds_constant (lb, ub); |
| make_decl_one_only (var, DECL_ASSEMBLER_NAME (var)); |
| /* We may use this symbol during ctors generation in chkp_finish_file |
| when all symbols are emitted. Force output to avoid undefined |
| symbols in ctors. */ |
| node = varpool_node::get_create (var); |
| node->force_output = 1; |
| |
| varpool_node::finalize_decl (var); |
| |
| return var; |
| } |
| |
| /* Generate code to make bounds with specified lower bound LB and SIZE. |
| if AFTER is 1 then code is inserted after position pointed by ITER |
| otherwise code is inserted before position pointed by ITER. |
| If ITER is NULL then code is added to entry block. */ |
| static tree |
| chkp_make_bounds (tree lb, tree size, gimple_stmt_iterator *iter, bool after) |
| { |
| gimple_seq seq; |
| gimple_stmt_iterator gsi; |
| gimple *stmt; |
| tree bounds; |
| |
| if (iter) |
| gsi = *iter; |
| else |
| gsi = gsi_start_bb (chkp_get_entry_block ()); |
| |
| seq = NULL; |
| |
| lb = chkp_force_gimple_call_op (lb, &seq); |
| size = chkp_force_gimple_call_op (size, &seq); |
| |
| stmt = gimple_build_call (chkp_bndmk_fndecl, 2, lb, size); |
| chkp_mark_stmt (stmt); |
| |
| bounds = chkp_get_tmp_reg (stmt); |
| gimple_call_set_lhs (stmt, bounds); |
| |
| gimple_seq_add_stmt (&seq, stmt); |
| |
| if (iter && after) |
| gsi_insert_seq_after (&gsi, seq, GSI_SAME_STMT); |
| else |
| gsi_insert_seq_before (&gsi, seq, GSI_SAME_STMT); |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Made bounds: "); |
| print_gimple_stmt (dump_file, stmt, 0, TDF_VOPS|TDF_MEMSYMS); |
| if (iter) |
| { |
| fprintf (dump_file, " inserted before statement: "); |
| print_gimple_stmt (dump_file, gsi_stmt (*iter), 0, TDF_VOPS|TDF_MEMSYMS); |
| } |
| else |
| fprintf (dump_file, " at function entry\n"); |
| } |
| |
| /* update_stmt (stmt); */ |
| |
| return bounds; |
| } |
| |
| /* Return var holding zero bounds. */ |
| tree |
| chkp_get_zero_bounds_var (void) |
| { |
| if (!chkp_zero_bounds_var) |
| chkp_zero_bounds_var |
| = chkp_make_static_const_bounds (0, -1, |
| CHKP_ZERO_BOUNDS_VAR_NAME); |
| return chkp_zero_bounds_var; |
| } |
| |
| /* Return var holding none bounds. */ |
| tree |
| chkp_get_none_bounds_var (void) |
| { |
| if (!chkp_none_bounds_var) |
| chkp_none_bounds_var |
| = chkp_make_static_const_bounds (-1, 0, |
| CHKP_NONE_BOUNDS_VAR_NAME); |
| return chkp_none_bounds_var; |
| } |
| |
| /* Return SSA_NAME used to represent zero bounds. */ |
| static tree |
| chkp_get_zero_bounds (void) |
| { |
| if (zero_bounds) |
| return zero_bounds; |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, "Creating zero bounds..."); |
| |
| if ((flag_chkp_use_static_bounds && flag_chkp_use_static_const_bounds) |
| || flag_chkp_use_static_const_bounds > 0) |
| { |
| gimple_stmt_iterator gsi = gsi_start_bb (chkp_get_entry_block ()); |
| gimple *stmt; |
| |
| zero_bounds = chkp_get_tmp_reg (NULL); |
| stmt = gimple_build_assign (zero_bounds, chkp_get_zero_bounds_var ()); |
| gsi_insert_before (&gsi, stmt, GSI_SAME_STMT); |
| } |
| else |
| zero_bounds = chkp_make_bounds (integer_zero_node, |
| integer_zero_node, |
| NULL, |
| false); |
| |
| return zero_bounds; |
| } |
| |
| /* Return SSA_NAME used to represent none bounds. */ |
| static tree |
| chkp_get_none_bounds (void) |
| { |
| if (none_bounds) |
| return none_bounds; |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| fprintf (dump_file, "Creating none bounds..."); |
| |
| |
| if ((flag_chkp_use_static_bounds && flag_chkp_use_static_const_bounds) |
| || flag_chkp_use_static_const_bounds > 0) |
| { |
| gimple_stmt_iterator gsi = gsi_start_bb (chkp_get_entry_block ()); |
| gimple *stmt; |
| |
| none_bounds = chkp_get_tmp_reg (NULL); |
| stmt = gimple_build_assign (none_bounds, chkp_get_none_bounds_var ()); |
| gsi_insert_before (&gsi, stmt, GSI_SAME_STMT); |
| } |
| else |
| none_bounds = chkp_make_bounds (integer_minus_one_node, |
| build_int_cst (size_type_node, 2), |
| NULL, |
| false); |
| |
| return none_bounds; |
| } |
| |
| /* Return bounds to be used as a result of operation which |
| should not create poiunter (e.g. MULT_EXPR). */ |
| static tree |
| chkp_get_invalid_op_bounds (void) |
| { |
| return chkp_get_zero_bounds (); |
| } |
| |
| /* Return bounds to be used for loads of non-pointer values. */ |
| static tree |
| chkp_get_nonpointer_load_bounds (void) |
| { |
| return chkp_get_zero_bounds (); |
| } |
| |
| /* Return 1 if may use bndret call to get bounds for pointer |
| returned by CALL. */ |
| static bool |
| chkp_call_returns_bounds_p (gcall *call) |
| { |
| if (gimple_call_internal_p (call)) |
| { |
| if (gimple_call_internal_fn (call) == IFN_VA_ARG) |
| return true; |
| return false; |
| } |
| |
| if (gimple_call_builtin_p (call, BUILT_IN_CHKP_NARROW_PTR_BOUNDS) |
| || chkp_gimple_call_builtin_p (call, BUILT_IN_CHKP_NARROW)) |
| return true; |
| |
| if (gimple_call_with_bounds_p (call)) |
| return true; |
| |
| tree fndecl = gimple_call_fndecl (call); |
| |
| if (fndecl && DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_MD) |
| return false; |
| |
| if (fndecl && !chkp_instrumentable_p (fndecl)) |
| return false; |
| |
| if (fndecl && DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_NORMAL) |
| { |
| if (chkp_instrument_normal_builtin (fndecl)) |
| return true; |
| |
| if (!lookup_attribute ("always_inline", DECL_ATTRIBUTES (fndecl))) |
| return false; |
| |
| struct cgraph_node *clone = chkp_maybe_create_clone (fndecl); |
| return (clone && gimple_has_body_p (clone->decl)); |
| } |
| |
| return true; |
| } |
| |
| /* Build bounds returned by CALL. */ |
| static tree |
| chkp_build_returned_bound (gcall *call) |
| { |
| gimple_stmt_iterator gsi; |
| tree bounds; |
| gimple *stmt; |
| tree fndecl = gimple_call_fndecl (call); |
| unsigned int retflags; |
| tree lhs = gimple_call_lhs (call); |
| |
| /* To avoid fixing alloca expands in targets we handle |
| it separately. */ |
| if (fndecl |
| && DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_NORMAL |
| && (DECL_FUNCTION_CODE (fndecl) == BUILT_IN_ALLOCA |
| || DECL_FUNCTION_CODE (fndecl) == BUILT_IN_ALLOCA_WITH_ALIGN)) |
| { |
| tree size = gimple_call_arg (call, 0); |
| gimple_stmt_iterator iter = gsi_for_stmt (call); |
| bounds = chkp_make_bounds (lhs, size, &iter, true); |
| } |
| /* We know bounds returned by set_bounds builtin call. */ |
| else if (fndecl |
| && DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_NORMAL |
| && DECL_FUNCTION_CODE (fndecl) == BUILT_IN_CHKP_SET_PTR_BOUNDS) |
| { |
| tree lb = gimple_call_arg (call, 0); |
| tree size = gimple_call_arg (call, 1); |
| gimple_stmt_iterator iter = gsi_for_stmt (call); |
| bounds = chkp_make_bounds (lb, size, &iter, true); |
| } |
| /* Detect bounds initialization calls. */ |
| else if (fndecl |
| && DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_NORMAL |
| && DECL_FUNCTION_CODE (fndecl) == BUILT_IN_CHKP_INIT_PTR_BOUNDS) |
| bounds = chkp_get_zero_bounds (); |
| /* Detect bounds nullification calls. */ |
| else if (fndecl |
| && DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_NORMAL |
| && DECL_FUNCTION_CODE (fndecl) == BUILT_IN_CHKP_NULL_PTR_BOUNDS) |
| bounds = chkp_get_none_bounds (); |
| /* Detect bounds copy calls. */ |
| else if (fndecl |
| && DECL_BUILT_IN_CLASS (fndecl) == BUILT_IN_NORMAL |
| && DECL_FUNCTION_CODE (fndecl) == BUILT_IN_CHKP_COPY_PTR_BOUNDS) |
| { |
| gimple_stmt_iterator iter = gsi_for_stmt (call); |
| bounds = chkp_find_bounds (gimple_call_arg (call, 1), &iter); |
| } |
| /* Do not use retbnd when returned bounds are equal to some |
| of passed bounds. */ |
| else if (((retflags = gimple_call_return_flags (call)) & ERF_RETURNS_ARG) |
| && (retflags & ERF_RETURN_ARG_MASK) < gimple_call_num_args (call)) |
| { |
| gimple_stmt_iterator iter = gsi_for_stmt (call); |
| unsigned int retarg = retflags & ERF_RETURN_ARG_MASK, argno; |
| if (gimple_call_with_bounds_p (call)) |
| { |
| for (argno = 0; argno < gimple_call_num_args (call); argno++) |
| if (!POINTER_BOUNDS_P (gimple_call_arg (call, argno))) |
| { |
| if (retarg) |
| retarg--; |
| else |
| break; |
| } |
| } |
| else |
| argno = retarg; |
| |
| bounds = chkp_find_bounds (gimple_call_arg (call, argno), &iter); |
| } |
| else if (chkp_call_returns_bounds_p (call) |
| && BOUNDED_P (lhs)) |
| { |
| gcc_assert (TREE_CODE (lhs) == SSA_NAME); |
| |
| /* In general case build checker builtin call to |
| obtain returned bounds. */ |
| stmt = gimple_build_call (chkp_ret_bnd_fndecl, 1, |
| gimple_call_lhs (call)); |
| chkp_mark_stmt (stmt); |
| |
| gsi = gsi_for_stmt (call); |
| gsi_insert_after (&gsi, stmt, GSI_SAME_STMT); |
| |
| bounds = chkp_get_tmp_reg (stmt); |
| gimple_call_set_lhs (stmt, bounds); |
| |
| update_stmt (stmt); |
| } |
| else |
| bounds = chkp_get_zero_bounds (); |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Built returned bounds ("); |
| print_generic_expr (dump_file, bounds, 0); |
| fprintf (dump_file, ") for call: "); |
| print_gimple_stmt (dump_file, call, 0, TDF_VOPS|TDF_MEMSYMS); |
| } |
| |
| bounds = chkp_maybe_copy_and_register_bounds (lhs, bounds); |
| |
| return bounds; |
| } |
| |
| /* Return bounds used as returned by call |
| which produced SSA name VAL. */ |
| gcall * |
| chkp_retbnd_call_by_val (tree val) |
| { |
| if (TREE_CODE (val) != SSA_NAME) |
| return NULL; |
| |
| gcc_assert (gimple_code (SSA_NAME_DEF_STMT (val)) == GIMPLE_CALL); |
| |
| imm_use_iterator use_iter; |
| use_operand_p use_p; |
| FOR_EACH_IMM_USE_FAST (use_p, use_iter, val) |
| if (chkp_gimple_call_builtin_p (USE_STMT (use_p), BUILT_IN_CHKP_BNDRET)) |
| return as_a <gcall *> (USE_STMT (use_p)); |
| |
| return NULL; |
| } |
| |
| /* Check the next parameter for the given PARM is bounds |
| and return it's default SSA_NAME (create if required). */ |
| static tree |
| chkp_get_next_bounds_parm (tree parm) |
| { |
| tree bounds = TREE_CHAIN (parm); |
| gcc_assert (POINTER_BOUNDS_P (bounds)); |
| bounds = ssa_default_def (cfun, bounds); |
| if (!bounds) |
| { |
| bounds = make_ssa_name (TREE_CHAIN (parm), gimple_build_nop ()); |
| set_ssa_default_def (cfun, TREE_CHAIN (parm), bounds); |
| } |
| return bounds; |
| } |
| |
| /* Return bounds to be used for input argument PARM. */ |
| static tree |
| chkp_get_bound_for_parm (tree parm) |
| { |
| tree decl = SSA_NAME_VAR (parm); |
| tree bounds; |
| |
| gcc_assert (TREE_CODE (decl) == PARM_DECL); |
| |
| bounds = chkp_get_registered_bounds (parm); |
| |
| if (!bounds) |
| bounds = chkp_get_registered_bounds (decl); |
| |
| if (!bounds) |
| { |
| tree orig_decl = cgraph_node::get (cfun->decl)->orig_decl; |
| |
| /* For static chain param we return zero bounds |
| because currently we do not check dereferences |
| of this pointer. */ |
| if (cfun->static_chain_decl == decl) |
| bounds = chkp_get_zero_bounds (); |
| /* If non instrumented runtime is used then it may be useful |
| to use zero bounds for input arguments of main |
| function. */ |
| else if (flag_chkp_zero_input_bounds_for_main |
| && strcmp (IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (orig_decl)), |
| "main") == 0) |
| bounds = chkp_get_zero_bounds (); |
| else if (BOUNDED_P (parm)) |
| { |
| bounds = chkp_get_next_bounds_parm (decl); |
| bounds = chkp_maybe_copy_and_register_bounds (decl, bounds); |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Built arg bounds ("); |
| print_generic_expr (dump_file, bounds, 0); |
| fprintf (dump_file, ") for arg: "); |
| print_node (dump_file, "", decl, 0); |
| } |
| } |
| else |
| bounds = chkp_get_zero_bounds (); |
| } |
| |
| if (!chkp_get_registered_bounds (parm)) |
| bounds = chkp_maybe_copy_and_register_bounds (parm, bounds); |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Using bounds "); |
| print_generic_expr (dump_file, bounds, 0); |
| fprintf (dump_file, " for parm "); |
| print_generic_expr (dump_file, parm, 0); |
| fprintf (dump_file, " of type "); |
| print_generic_expr (dump_file, TREE_TYPE (parm), 0); |
| fprintf (dump_file, ".\n"); |
| } |
| |
| return bounds; |
| } |
| |
| /* Build and return CALL_EXPR for bndstx builtin with specified |
| arguments. */ |
| tree |
| chkp_build_bndldx_call (tree addr, tree ptr) |
| { |
| tree fn = build1 (ADDR_EXPR, |
| build_pointer_type (TREE_TYPE (chkp_bndldx_fndecl)), |
| chkp_bndldx_fndecl); |
| tree call = build_call_nary (TREE_TYPE (TREE_TYPE (chkp_bndldx_fndecl)), |
| fn, 2, addr, ptr); |
| CALL_WITH_BOUNDS_P (call) = true; |
| return call; |
| } |
| |
| /* Insert code to load bounds for PTR located by ADDR. |
| Code is inserted after position pointed by GSI. |
| Loaded bounds are returned. */ |
| static tree |
| chkp_build_bndldx (tree addr, tree ptr, gimple_stmt_iterator *gsi) |
| { |
| gimple_seq seq; |
| gimple *stmt; |
| tree bounds; |
| |
| seq = NULL; |
| |
| addr = chkp_force_gimple_call_op (addr, &seq); |
| ptr = chkp_force_gimple_call_op (ptr, &seq); |
| |
| stmt = gimple_build_call (chkp_bndldx_fndecl, 2, addr, ptr); |
| chkp_mark_stmt (stmt); |
| bounds = chkp_get_tmp_reg (stmt); |
| gimple_call_set_lhs (stmt, bounds); |
| |
| gimple_seq_add_stmt (&seq, stmt); |
| |
| gsi_insert_seq_after (gsi, seq, GSI_CONTINUE_LINKING); |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Generated bndldx for pointer "); |
| print_generic_expr (dump_file, ptr, 0); |
| fprintf (dump_file, ": "); |
| print_gimple_stmt (dump_file, stmt, 0, TDF_VOPS|TDF_MEMSYMS); |
| } |
| |
| return bounds; |
| } |
| |
| /* Build and return CALL_EXPR for bndstx builtin with specified |
| arguments. */ |
| tree |
| chkp_build_bndstx_call (tree addr, tree ptr, tree bounds) |
| { |
| tree fn = build1 (ADDR_EXPR, |
| build_pointer_type (TREE_TYPE (chkp_bndstx_fndecl)), |
| chkp_bndstx_fndecl); |
| tree call = build_call_nary (TREE_TYPE (TREE_TYPE (chkp_bndstx_fndecl)), |
| fn, 3, ptr, bounds, addr); |
| CALL_WITH_BOUNDS_P (call) = true; |
| return call; |
| } |
| |
| /* Insert code to store BOUNDS for PTR stored by ADDR. |
| New statements are inserted after position pointed |
| by GSI. */ |
| void |
| chkp_build_bndstx (tree addr, tree ptr, tree bounds, |
| gimple_stmt_iterator *gsi) |
| { |
| gimple_seq seq; |
| gimple *stmt; |
| |
| seq = NULL; |
| |
| addr = chkp_force_gimple_call_op (addr, &seq); |
| ptr = chkp_force_gimple_call_op (ptr, &seq); |
| |
| stmt = gimple_build_call (chkp_bndstx_fndecl, 3, ptr, bounds, addr); |
| chkp_mark_stmt (stmt); |
| gimple_call_set_with_bounds (stmt, true); |
| |
| gimple_seq_add_stmt (&seq, stmt); |
| |
| gsi_insert_seq_after (gsi, seq, GSI_CONTINUE_LINKING); |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Generated bndstx for pointer store "); |
| print_gimple_stmt (dump_file, gsi_stmt (*gsi), 0, TDF_VOPS|TDF_MEMSYMS); |
| print_gimple_stmt (dump_file, stmt, 2, TDF_VOPS|TDF_MEMSYMS); |
| } |
| } |
| |
| /* This function is called when call statement |
| is inlined and therefore we can't use bndret |
| for its LHS anymore. Function fixes bndret |
| call using new RHS value if possible. */ |
| void |
| chkp_fixup_inlined_call (tree lhs, tree rhs) |
| { |
| tree addr, bounds; |
| gcall *retbnd, *bndldx; |
| |
| if (!BOUNDED_P (lhs)) |
| return; |
| |
| /* Search for retbnd call. */ |
| retbnd = chkp_retbnd_call_by_val (lhs); |
| if (!retbnd) |
| return; |
| |
| /* Currently only handle cases when call is replaced |
| with a memory access. In this case bndret call |
| may be replaced with bndldx call. Otherwise we |
| have to search for bounds which may cause wrong |
| result due to various optimizations applied. */ |
| switch (TREE_CODE (rhs)) |
| { |
| case VAR_DECL: |
| if (DECL_REGISTER (rhs)) |
| return; |
| break; |
| |
| case MEM_REF: |
| break; |
| |
| case ARRAY_REF: |
| case COMPONENT_REF: |
| addr = get_base_address (rhs); |
| if (!DECL_P (addr) |
| && TREE_CODE (addr) != MEM_REF) |
| return; |
| if (DECL_P (addr) && DECL_REGISTER (addr)) |
| return; |
| break; |
| |
| default: |
| return; |
| } |
| |
| /* Create a new statements sequence with bndldx call. */ |
| gimple_stmt_iterator gsi = gsi_for_stmt (retbnd); |
| addr = build_fold_addr_expr (rhs); |
| chkp_build_bndldx (addr, lhs, &gsi); |
| bndldx = as_a <gcall *> (gsi_stmt (gsi)); |
| |
| /* Remove bndret call. */ |
| bounds = gimple_call_lhs (retbnd); |
| gsi = gsi_for_stmt (retbnd); |
| gsi_remove (&gsi, true); |
| |
| /* Link new bndldx call. */ |
| gimple_call_set_lhs (bndldx, bounds); |
| update_stmt (bndldx); |
| } |
| |
| /* Compute bounds for pointer NODE which was assigned in |
| assignment statement ASSIGN. Return computed bounds. */ |
| static tree |
| chkp_compute_bounds_for_assignment (tree node, gimple *assign) |
| { |
| enum tree_code rhs_code = gimple_assign_rhs_code (assign); |
| tree rhs1 = gimple_assign_rhs1 (assign); |
| tree bounds = NULL_TREE; |
| gimple_stmt_iterator iter = gsi_for_stmt (assign); |
| tree base = NULL; |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Computing bounds for assignment: "); |
| print_gimple_stmt (dump_file, assign, 0, TDF_VOPS|TDF_MEMSYMS); |
| } |
| |
| switch (rhs_code) |
| { |
| case MEM_REF: |
| case TARGET_MEM_REF: |
| case COMPONENT_REF: |
| case ARRAY_REF: |
| /* We need to load bounds from the bounds table. */ |
| bounds = chkp_find_bounds_loaded (node, rhs1, &iter); |
| break; |
| |
| case VAR_DECL: |
| case SSA_NAME: |
| case ADDR_EXPR: |
| case POINTER_PLUS_EXPR: |
| case NOP_EXPR: |
| case CONVERT_EXPR: |
| case INTEGER_CST: |
| /* Bounds are just propagated from RHS. */ |
| bounds = chkp_find_bounds (rhs1, &iter); |
| base = rhs1; |
| break; |
| |
| case VIEW_CONVERT_EXPR: |
| /* Bounds are just propagated from RHS. */ |
| bounds = chkp_find_bounds (TREE_OPERAND (rhs1, 0), &iter); |
| break; |
| |
| case PARM_DECL: |
| if (BOUNDED_P (rhs1)) |
| { |
| /* We need to load bounds from the bounds table. */ |
| bounds = chkp_build_bndldx (chkp_build_addr_expr (rhs1), |
| node, &iter); |
| TREE_ADDRESSABLE (rhs1) = 1; |
| } |
| else |
| bounds = chkp_get_nonpointer_load_bounds (); |
| break; |
| |
| case MINUS_EXPR: |
| case PLUS_EXPR: |
| case BIT_AND_EXPR: |
| case BIT_IOR_EXPR: |
| case BIT_XOR_EXPR: |
| { |
| tree rhs2 = gimple_assign_rhs2 (assign); |
| tree bnd1 = chkp_find_bounds (rhs1, &iter); |
| tree bnd2 = chkp_find_bounds (rhs2, &iter); |
| |
| /* First we try to check types of operands. If it |
| does not help then look at bound values. |
| |
| If some bounds are incomplete and other are |
| not proven to be valid (i.e. also incomplete |
| or invalid because value is not pointer) then |
| resulting value is incomplete and will be |
| recomputed later in chkp_finish_incomplete_bounds. */ |
| if (BOUNDED_P (rhs1) |
| && !BOUNDED_P (rhs2)) |
| bounds = bnd1; |
| else if (BOUNDED_P (rhs2) |
| && !BOUNDED_P (rhs1) |
| && rhs_code != MINUS_EXPR) |
| bounds = bnd2; |
| else if (chkp_incomplete_bounds (bnd1)) |
| if (chkp_valid_bounds (bnd2) && rhs_code != MINUS_EXPR |
| && !chkp_incomplete_bounds (bnd2)) |
| bounds = bnd2; |
| else |
| bounds = incomplete_bounds; |
| else if (chkp_incomplete_bounds (bnd2)) |
| if (chkp_valid_bounds (bnd1) |
| && !chkp_incomplete_bounds (bnd1)) |
| bounds = bnd1; |
| else |
| bounds = incomplete_bounds; |
| else if (!chkp_valid_bounds (bnd1)) |
| if (chkp_valid_bounds (bnd2) && rhs_code != MINUS_EXPR) |
| bounds = bnd2; |
| else if (bnd2 == chkp_get_zero_bounds ()) |
| bounds = bnd2; |
| else |
| bounds = bnd1; |
| else if (!chkp_valid_bounds (bnd2)) |
| bounds = bnd1; |
| else |
| /* Seems both operands may have valid bounds |
| (e.g. pointer minus pointer). In such case |
| use default invalid op bounds. */ |
| bounds = chkp_get_invalid_op_bounds (); |
| |
| base = (bounds == bnd1) ? rhs1 : (bounds == bnd2) ? rhs2 : NULL; |
| } |
| break; |
| |
| case BIT_NOT_EXPR: |
| case NEGATE_EXPR: |
| case LSHIFT_EXPR: |
| case RSHIFT_EXPR: |
| case LROTATE_EXPR: |
| case RROTATE_EXPR: |
| case EQ_EXPR: |
| case NE_EXPR: |
| case LT_EXPR: |
| case LE_EXPR: |
| case GT_EXPR: |
| case GE_EXPR: |
| case MULT_EXPR: |
| case RDIV_EXPR: |
| case TRUNC_DIV_EXPR: |
| case FLOOR_DIV_EXPR: |
| case CEIL_DIV_EXPR: |
| case ROUND_DIV_EXPR: |
| case TRUNC_MOD_EXPR: |
| case FLOOR_MOD_EXPR: |
| case CEIL_MOD_EXPR: |
| case ROUND_MOD_EXPR: |
| case EXACT_DIV_EXPR: |
| case FIX_TRUNC_EXPR: |
| case FLOAT_EXPR: |
| case REALPART_EXPR: |
| case IMAGPART_EXPR: |
| /* No valid bounds may be produced by these exprs. */ |
| bounds = chkp_get_invalid_op_bounds (); |
| break; |
| |
| case COND_EXPR: |
| { |
| tree val1 = gimple_assign_rhs2 (assign); |
| tree val2 = gimple_assign_rhs3 (assign); |
| tree bnd1 = chkp_find_bounds (val1, &iter); |
| tree bnd2 = chkp_find_bounds (val2, &iter); |
| gimple *stmt; |
| |
| if (chkp_incomplete_bounds (bnd1) || chkp_incomplete_bounds (bnd2)) |
| bounds = incomplete_bounds; |
| else if (bnd1 == bnd2) |
| bounds = bnd1; |
| else |
| { |
| rhs1 = unshare_expr (rhs1); |
| |
| bounds = chkp_get_tmp_reg (assign); |
| stmt = gimple_build_assign (bounds, COND_EXPR, rhs1, bnd1, bnd2); |
| gsi_insert_after (&iter, stmt, GSI_SAME_STMT); |
| |
| if (!chkp_valid_bounds (bnd1) && !chkp_valid_bounds (bnd2)) |
| chkp_mark_invalid_bounds (bounds); |
| } |
| } |
| break; |
| |
| case MAX_EXPR: |
| case MIN_EXPR: |
| { |
| tree rhs2 = gimple_assign_rhs2 (assign); |
| tree bnd1 = chkp_find_bounds (rhs1, &iter); |
| tree bnd2 = chkp_find_bounds (rhs2, &iter); |
| |
| if (chkp_incomplete_bounds (bnd1) || chkp_incomplete_bounds (bnd2)) |
| bounds = incomplete_bounds; |
| else if (bnd1 == bnd2) |
| bounds = bnd1; |
| else |
| { |
| gimple *stmt; |
| tree cond = build2 (rhs_code == MAX_EXPR ? GT_EXPR : LT_EXPR, |
| boolean_type_node, rhs1, rhs2); |
| bounds = chkp_get_tmp_reg (assign); |
| stmt = gimple_build_assign (bounds, COND_EXPR, cond, bnd1, bnd2); |
| |
| gsi_insert_after (&iter, stmt, GSI_SAME_STMT); |
| |
| if (!chkp_valid_bounds (bnd1) && !chkp_valid_bounds (bnd2)) |
| chkp_mark_invalid_bounds (bounds); |
| } |
| } |
| break; |
| |
| default: |
| bounds = chkp_get_zero_bounds (); |
| warning (0, "pointer bounds were lost due to unexpected expression %s", |
| get_tree_code_name (rhs_code)); |
| } |
| |
| gcc_assert (bounds); |
| |
| /* We may reuse bounds of other pointer we copy/modify. But it is not |
| allowed for abnormal ssa names. If we produced a pointer using |
| abnormal ssa name, we better make a bounds copy to avoid coalescing |
| issues. */ |
| if (base |
| && TREE_CODE (base) == SSA_NAME |
| && SSA_NAME_OCCURS_IN_ABNORMAL_PHI (base)) |
| { |
| gimple *stmt = gimple_build_assign (chkp_get_tmp_reg (NULL), bounds); |
| gsi_insert_after (&iter, stmt, GSI_SAME_STMT); |
| bounds = gimple_assign_lhs (stmt); |
| } |
| |
| if (node) |
| bounds = chkp_maybe_copy_and_register_bounds (node, bounds); |
| |
| return bounds; |
| } |
| |
| /* Compute bounds for ssa name NODE defined by DEF_STMT pointed by ITER. |
| |
| There are just few statement codes allowed: NOP (for default ssa names), |
| ASSIGN, CALL, PHI, ASM. |
| |
| Return computed bounds. */ |
| static tree |
| chkp_get_bounds_by_definition (tree node, gimple *def_stmt, |
| gphi_iterator *iter) |
| { |
| tree var, bounds; |
| enum gimple_code code = gimple_code (def_stmt); |
| gphi *stmt; |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Searching for bounds for node: "); |
| print_generic_expr (dump_file, node, 0); |
| |
| fprintf (dump_file, " using its definition: "); |
| print_gimple_stmt (dump_file, def_stmt, 0, TDF_VOPS|TDF_MEMSYMS); |
| } |
| |
| switch (code) |
| { |
| case GIMPLE_NOP: |
| var = SSA_NAME_VAR (node); |
| switch (TREE_CODE (var)) |
| { |
| case PARM_DECL: |
| bounds = chkp_get_bound_for_parm (node); |
| break; |
| |
| case VAR_DECL: |
| /* For uninitialized pointers use none bounds. */ |
| bounds = chkp_get_none_bounds (); |
| bounds = chkp_maybe_copy_and_register_bounds (node, bounds); |
| break; |
| |
| case RESULT_DECL: |
| { |
| tree base_type; |
| |
| gcc_assert (TREE_CODE (TREE_TYPE (node)) == REFERENCE_TYPE); |
| |
| base_type = TREE_TYPE (TREE_TYPE (node)); |
| |
| gcc_assert (TYPE_SIZE (base_type) |
| && TREE_CODE (TYPE_SIZE (base_type)) == INTEGER_CST |
| && tree_to_uhwi (TYPE_SIZE (base_type)) != 0); |
| |
| bounds = chkp_make_bounds (node, TYPE_SIZE_UNIT (base_type), |
| NULL, false); |
| bounds = chkp_maybe_copy_and_register_bounds (node, bounds); |
| } |
| break; |
| |
| default: |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Unexpected var with no definition\n"); |
| print_generic_expr (dump_file, var, 0); |
| } |
| internal_error ("chkp_get_bounds_by_definition: Unexpected var of type %s", |
| get_tree_code_name (TREE_CODE (var))); |
| } |
| break; |
| |
| case GIMPLE_ASSIGN: |
| bounds = chkp_compute_bounds_for_assignment (node, def_stmt); |
| break; |
| |
| case GIMPLE_CALL: |
| bounds = chkp_build_returned_bound (as_a <gcall *> (def_stmt)); |
| break; |
| |
| case GIMPLE_PHI: |
| if (SSA_NAME_OCCURS_IN_ABNORMAL_PHI (node)) |
| if (SSA_NAME_VAR (node)) |
| var = chkp_get_bounds_var (SSA_NAME_VAR (node)); |
| else |
| var = make_temp_ssa_name (pointer_bounds_type_node, |
| NULL, |
| CHKP_BOUND_TMP_NAME); |
| else |
| var = chkp_get_tmp_var (); |
| stmt = create_phi_node (var, gimple_bb (def_stmt)); |
| bounds = gimple_phi_result (stmt); |
| *iter = gsi_for_phi (stmt); |
| |
| bounds = chkp_maybe_copy_and_register_bounds (node, bounds); |
| |
| /* Created bounds do not have all phi args computed and |
| therefore we do not know if there is a valid source |
| of bounds for that node. Therefore we mark bounds |
| as incomplete and then recompute them when all phi |
| args are computed. */ |
| chkp_register_incomplete_bounds (bounds, node); |
| break; |
| |
| case GIMPLE_ASM: |
| bounds = chkp_get_zero_bounds (); |
| bounds = chkp_maybe_copy_and_register_bounds (node, bounds); |
| break; |
| |
| default: |
| internal_error ("chkp_get_bounds_by_definition: Unexpected GIMPLE code %s", |
| gimple_code_name[code]); |
| } |
| |
| return bounds; |
| } |
| |
| /* Return CALL_EXPR for bndmk with specified LOWER_BOUND and SIZE. */ |
| tree |
| chkp_build_make_bounds_call (tree lower_bound, tree size) |
| { |
| tree call = build1 (ADDR_EXPR, |
| build_pointer_type (TREE_TYPE (chkp_bndmk_fndecl)), |
| chkp_bndmk_fndecl); |
| return build_call_nary (TREE_TYPE (TREE_TYPE (chkp_bndmk_fndecl)), |
| call, 2, lower_bound, size); |
| } |
| |
| /* Create static bounds var of specfified OBJ which is |
| is either VAR_DECL or string constant. */ |
| static tree |
| chkp_make_static_bounds (tree obj) |
| { |
| static int string_id = 1; |
| static int var_id = 1; |
| tree *slot; |
| const char *var_name; |
| char *bnd_var_name; |
| tree bnd_var; |
| |
| /* First check if we already have required var. */ |
| if (chkp_static_var_bounds) |
| { |
| /* For vars we use assembler name as a key in |
| chkp_static_var_bounds map. It allows to |
| avoid duplicating bound vars for decls |
| sharing assembler name. */ |
| if (VAR_P (obj)) |
| { |
| tree name = DECL_ASSEMBLER_NAME (obj); |
| slot = chkp_static_var_bounds->get (name); |
| if (slot) |
| return *slot; |
| } |
| else |
| { |
| slot = chkp_static_var_bounds->get (obj); |
| if (slot) |
| return *slot; |
| } |
| } |
| |
| /* Build decl for bounds var. */ |
| if (VAR_P (obj)) |
| { |
| if (DECL_IGNORED_P (obj)) |
| { |
| bnd_var_name = (char *) xmalloc (strlen (CHKP_VAR_BOUNDS_PREFIX) + 10); |
| sprintf (bnd_var_name, "%s%d", CHKP_VAR_BOUNDS_PREFIX, var_id++); |
| } |
| else |
| { |
| var_name = IDENTIFIER_POINTER (DECL_ASSEMBLER_NAME (obj)); |
| |
| /* For hidden symbols we want to skip first '*' char. */ |
| if (*var_name == '*') |
| var_name++; |
| |
| bnd_var_name = (char *) xmalloc (strlen (var_name) |
| + strlen (CHKP_BOUNDS_OF_SYMBOL_PREFIX) + 1); |
| strcpy (bnd_var_name, CHKP_BOUNDS_OF_SYMBOL_PREFIX); |
| strcat (bnd_var_name, var_name); |
| } |
| |
| bnd_var = build_decl (UNKNOWN_LOCATION, VAR_DECL, |
| get_identifier (bnd_var_name), |
| pointer_bounds_type_node); |
| |
| /* Address of the obj will be used as lower bound. */ |
| TREE_ADDRESSABLE (obj) = 1; |
| } |
| else |
| { |
| bnd_var_name = (char *) xmalloc (strlen (CHKP_STRING_BOUNDS_PREFIX) + 10); |
| sprintf (bnd_var_name, "%s%d", CHKP_STRING_BOUNDS_PREFIX, string_id++); |
| |
| bnd_var = build_decl (UNKNOWN_LOCATION, VAR_DECL, |
| get_identifier (bnd_var_name), |
| pointer_bounds_type_node); |
| } |
| |
| free (bnd_var_name); |
| |
| TREE_PUBLIC (bnd_var) = 0; |
| TREE_USED (bnd_var) = 1; |
| TREE_READONLY (bnd_var) = 0; |
| TREE_STATIC (bnd_var) = 1; |
| TREE_ADDRESSABLE (bnd_var) = 0; |
| DECL_ARTIFICIAL (bnd_var) = 1; |
| DECL_COMMON (bnd_var) = 1; |
| DECL_COMDAT (bnd_var) = 1; |
| DECL_READ_P (bnd_var) = 1; |
| DECL_INITIAL (bnd_var) = chkp_build_addr_expr (obj); |
| /* Force output similar to constant bounds. |
| See chkp_make_static_const_bounds. */ |
| varpool_node::get_create (bnd_var)->force_output = 1; |
| /* Mark symbol as requiring bounds initialization. */ |
| varpool_node::get_create (bnd_var)->need_bounds_init = 1; |
| varpool_node::finalize_decl (bnd_var); |
| |
| /* Add created var to the map to use it for other references |
| to obj. */ |
| if (!chkp_static_var_bounds) |
| chkp_static_var_bounds = new hash_map<tree, tree>; |
| |
| if (VAR_P (obj)) |
| { |
| tree name = DECL_ASSEMBLER_NAME (obj); |
| chkp_static_var_bounds->put (name, bnd_var); |
| } |
| else |
| chkp_static_var_bounds->put (obj, bnd_var); |
| |
| return bnd_var; |
| } |
| |
| /* When var has incomplete type we cannot get size to |
| compute its bounds. In such cases we use checker |
| builtin call which determines object size at runtime. */ |
| static tree |
| chkp_generate_extern_var_bounds (tree var) |
| { |
| tree bounds, size_reloc, lb, size, max_size, cond; |
| gimple_stmt_iterator gsi; |
| gimple_seq seq = NULL; |
| gimple *stmt; |
| |
| /* If instrumentation is not enabled for vars having |
| incomplete type then just return zero bounds to avoid |
| checks for this var. */ |
| if (!flag_chkp_incomplete_type) |
| return chkp_get_zero_bounds (); |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Generating bounds for extern symbol '"); |
| print_generic_expr (dump_file, var, 0); |
| fprintf (dump_file, "'\n"); |
| } |
| |
| stmt = gimple_build_call (chkp_sizeof_fndecl, 1, var); |
| |
| size_reloc = create_tmp_reg (chkp_uintptr_type, CHKP_SIZE_TMP_NAME); |
| gimple_call_set_lhs (stmt, size_reloc); |
| |
| gimple_seq_add_stmt (&seq, stmt); |
| |
| lb = chkp_build_addr_expr (var); |
| size = make_ssa_name (chkp_get_size_tmp_var ()); |
| |
| if (flag_chkp_zero_dynamic_size_as_infinite) |
| { |
| /* We should check that size relocation was resolved. |
| If it was not then use maximum possible size for the var. */ |
| max_size = build2 (MINUS_EXPR, chkp_uintptr_type, integer_zero_node, |
| fold_convert (chkp_uintptr_type, lb)); |
| max_size = chkp_force_gimple_call_op (max_size, &seq); |
| |
| cond = build2 (NE_EXPR, boolean_type_node, |
| size_reloc, integer_zero_node); |
| stmt = gimple_build_assign (size, COND_EXPR, cond, size_reloc, max_size); |
| gimple_seq_add_stmt (&seq, stmt); |
| } |
| else |
| { |
| stmt = gimple_build_assign (size, size_reloc); |
| gimple_seq_add_stmt (&seq, stmt); |
| } |
| |
| gsi = gsi_start_bb (chkp_get_entry_block ()); |
| gsi_insert_seq_after (&gsi, seq, GSI_CONTINUE_LINKING); |
| |
| bounds = chkp_make_bounds (lb, size, &gsi, true); |
| |
| return bounds; |
| } |
| |
| /* Return 1 if TYPE has fields with zero size or fields |
| marked with chkp_variable_size attribute. */ |
| bool |
| chkp_variable_size_type (tree type) |
| { |
| bool res = false; |
| tree field; |
| |
| if (RECORD_OR_UNION_TYPE_P (type)) |
| for (field = TYPE_FIELDS (type); field; field = DECL_CHAIN (field)) |
| { |
| if (TREE_CODE (field) == FIELD_DECL) |
| res = res |
| || lookup_attribute ("bnd_variable_size", DECL_ATTRIBUTES (field)) |
| || chkp_variable_size_type (TREE_TYPE (field)); |
| } |
| else |
| res = !TYPE_SIZE (type) |
| || TREE_CODE (TYPE_SIZE (type)) != INTEGER_CST |
| || tree_to_uhwi (TYPE_SIZE (type)) == 0; |
| |
| return res; |
| } |
| |
| /* Compute and return bounds for address of DECL which is |
| one of VAR_DECL, PARM_DECL, RESULT_DECL. */ |
| static tree |
| chkp_get_bounds_for_decl_addr (tree decl) |
| { |
| tree bounds; |
| |
| gcc_assert (VAR_P (decl) |
| || TREE_CODE (decl) == PARM_DECL |
| || TREE_CODE (decl) == RESULT_DECL); |
| |
| bounds = chkp_get_registered_addr_bounds (decl); |
| |
| if (bounds) |
| return bounds; |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Building bounds for address of decl "); |
| print_generic_expr (dump_file, decl, 0); |
| fprintf (dump_file, "\n"); |
| } |
| |
| /* Use zero bounds if size is unknown and checks for |
| unknown sizes are restricted. */ |
| if ((!DECL_SIZE (decl) |
| || (chkp_variable_size_type (TREE_TYPE (decl)) |
| && (TREE_STATIC (decl) |
| || DECL_EXTERNAL (decl) |
| || TREE_PUBLIC (decl)))) |
| && !flag_chkp_incomplete_type) |
| return chkp_get_zero_bounds (); |
| |
| if (flag_chkp_use_static_bounds |
| && VAR_P (decl) |
| && (TREE_STATIC (decl) |
| || DECL_EXTERNAL (decl) |
| || TREE_PUBLIC (decl)) |
| && !DECL_THREAD_LOCAL_P (decl)) |
| { |
| tree bnd_var = chkp_make_static_bounds (decl); |
| gimple_stmt_iterator gsi = gsi_start_bb (chkp_get_entry_block ()); |
| gimple *stmt; |
| |
| bounds = chkp_get_tmp_reg (NULL); |
| stmt = gimple_build_assign (bounds, bnd_var); |
| gsi_insert_before (&gsi, stmt, GSI_SAME_STMT); |
| } |
| else if (!DECL_SIZE (decl) |
| || (chkp_variable_size_type (TREE_TYPE (decl)) |
| && (TREE_STATIC (decl) |
| || DECL_EXTERNAL (decl) |
| || TREE_PUBLIC (decl)))) |
| { |
| gcc_assert (VAR_P (decl)); |
| bounds = chkp_generate_extern_var_bounds (decl); |
| } |
| else |
| { |
| tree lb = chkp_build_addr_expr (decl); |
| bounds = chkp_make_bounds (lb, DECL_SIZE_UNIT (decl), NULL, false); |
| } |
| |
| return bounds; |
| } |
| |
| /* Compute and return bounds for constant string. */ |
| static tree |
| chkp_get_bounds_for_string_cst (tree cst) |
| { |
| tree bounds; |
| tree lb; |
| tree size; |
| |
| gcc_assert (TREE_CODE (cst) == STRING_CST); |
| |
| bounds = chkp_get_registered_bounds (cst); |
| |
| if (bounds) |
| return bounds; |
| |
| if ((flag_chkp_use_static_bounds && flag_chkp_use_static_const_bounds) |
| || flag_chkp_use_static_const_bounds > 0) |
| { |
| tree bnd_var = chkp_make_static_bounds (cst); |
| gimple_stmt_iterator gsi = gsi_start_bb (chkp_get_entry_block ()); |
| gimple *stmt; |
| |
| bounds = chkp_get_tmp_reg (NULL); |
| stmt = gimple_build_assign (bounds, bnd_var); |
| gsi_insert_before (&gsi, stmt, GSI_SAME_STMT); |
| } |
| else |
| { |
| lb = chkp_build_addr_expr (cst); |
| size = build_int_cst (chkp_uintptr_type, TREE_STRING_LENGTH (cst)); |
| bounds = chkp_make_bounds (lb, size, NULL, false); |
| } |
| |
| bounds = chkp_maybe_copy_and_register_bounds (cst, bounds); |
| |
| return bounds; |
| } |
| |
| /* Generate code to instersect bounds BOUNDS1 and BOUNDS2 and |
| return the result. if ITER is not NULL then Code is inserted |
| before position pointed by ITER. Otherwise code is added to |
| entry block. */ |
| static tree |
| chkp_intersect_bounds (tree bounds1, tree bounds2, gimple_stmt_iterator *iter) |
| { |
| if (!bounds1 || bounds1 == chkp_get_zero_bounds ()) |
| return bounds2 ? bounds2 : bounds1; |
| else if (!bounds2 || bounds2 == chkp_get_zero_bounds ()) |
| return bounds1; |
| else |
| { |
| gimple_seq seq; |
| gimple *stmt; |
| tree bounds; |
| |
| seq = NULL; |
| |
| stmt = gimple_build_call (chkp_intersect_fndecl, 2, bounds1, bounds2); |
| chkp_mark_stmt (stmt); |
| |
| bounds = chkp_get_tmp_reg (stmt); |
| gimple_call_set_lhs (stmt, bounds); |
| |
| gimple_seq_add_stmt (&seq, stmt); |
| |
| /* We are probably doing narrowing for constant expression. |
| In such case iter may be undefined. */ |
| if (!iter) |
| { |
| gimple_stmt_iterator gsi = gsi_last_bb (chkp_get_entry_block ()); |
| iter = &gsi; |
| gsi_insert_seq_after (iter, seq, GSI_SAME_STMT); |
| } |
| else |
| gsi_insert_seq_before (iter, seq, GSI_SAME_STMT); |
| |
| if (dump_file && (dump_flags & TDF_DETAILS)) |
| { |
| fprintf (dump_file, "Bounds intersection: "); |
| print_gimple_stmt (dump_file, stmt, 0, TDF_VOPS|TDF_MEMSYMS); |
| fprintf (dump_file, " inserted before statement: "); |
| print_gimple_stmt (dump_file, gsi_stmt (*iter), 0, |
| TDF_VOPS|TDF_MEMSYMS); |
| } |
| |
| return bounds; |
| } |
| } |
| |
| /* Return 1 if we are allowed to narrow bounds for addressed FIELD |
| and 0 othersize. REF is reference to the field. */ |
| |
| static bool |
| chkp_may_narrow_to_field (tree ref, tree field) |
| { |
| return DECL_SIZE (field) && TREE_CODE (DECL_SIZE (field)) == INTEGER_CST |
| && tree_to_uhwi (DECL_SIZE (field)) != 0 |
| && !(flag_chkp_flexible_struct_trailing_arrays |
| && array_at_struct_end_p (ref)) |
| && (!DECL_FIELD_OFFSET (field) |
| || TREE_CODE (DECL_FIELD_OFFSET (field)) == INTEGER_CST) |
| && (!DECL_FIELD_BIT_OFFSET (field) |
| || TREE_CODE (DECL_FIELD_BIT_OFFSET (field)) == INTEGER_CST) |
| && !lookup_attribute ("bnd_variable_size", DECL_ATTRIBUTES (field)) |
| && !chkp_variable_size_type (TREE_TYPE (field)); |
| } |
| |
| /* Return 1 if bounds for FIELD should be narrowed to |
| field's own size. REF is reference to the field. */ |
| |
| static bool |
| chkp_narrow_bounds_for_field (tree ref, tree field) |
| { |
| HOST_WIDE_INT offs; |
| HOST_WIDE_INT bit_offs; |
| |
| if (!chkp_may_narrow_to_field (ref, field)) |
| return false; |
| |
| /* Access to compiler generated fields should not cause |
| bounds narrowing. */ |
| if (DECL_ARTIFICIAL (field)) |
| return false; |
| |
| offs = tree_to_uhwi (DECL_FIELD_OFFSET (field)); |
| bit_offs = tree_to_uhwi (DECL_FIELD_BIT_OFFSET (field)); |
| |
| return (flag_chkp_narrow_bounds |
| && (flag_chkp_first_field_has_own_bounds |
| || offs |
| || bit_offs)); |
| } |
| |
| /* Perform narrowing for BOUNDS of an INNER reference. Shift boundary |
| by OFFSET bytes and limit to SIZE bytes. Newly created statements are |
| added to ITER. */ |
| |
| static tree |
| chkp_narrow_size_and_offset (tree bounds, tree inner, tree offset, |
| tree size, gimple_stmt_iterator *iter) |
| { |
| tree addr = chkp_build_addr_expr (unshare_expr (inner)); |
| tree t = TREE_TYPE (addr); |
| |
| gimple *stmt = gimple_build_assign (NULL_TREE, addr); |
| addr = make_temp_ssa_name (t, stmt, CHKP_BOUND_TMP_NAME); |
| gimple_assign_set_lhs (stmt, addr); |
|