| /* |
| * This file is part of ltrace. |
| * Copyright (C) 2011,2012,2013 Petr Machata, Red Hat Inc. |
| * Copyright (C) 2007,2008 Juan Cespedes |
| * |
| * This program 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 2 of the |
| * License, or (at your option) any later version. |
| * |
| * This program 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 this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA |
| * 02110-1301 USA |
| */ |
| |
| #include <assert.h> |
| #include <stdlib.h> |
| #include <limits.h> |
| |
| #include "type.h" |
| #include "sysdep.h" |
| #include "expr.h" |
| #include "lens.h" |
| |
| struct arg_type_info * |
| type_get_simple(enum arg_type type) |
| { |
| #define HANDLE(T) { \ |
| static struct arg_type_info t = { T }; \ |
| case T: \ |
| return &t; \ |
| } |
| |
| switch (type) { |
| HANDLE(ARGTYPE_VOID) |
| HANDLE(ARGTYPE_INT) |
| HANDLE(ARGTYPE_UINT) |
| HANDLE(ARGTYPE_LONG) |
| HANDLE(ARGTYPE_ULONG) |
| HANDLE(ARGTYPE_CHAR) |
| HANDLE(ARGTYPE_SHORT) |
| HANDLE(ARGTYPE_USHORT) |
| HANDLE(ARGTYPE_FLOAT) |
| HANDLE(ARGTYPE_DOUBLE) |
| |
| #undef HANDLE |
| |
| case ARGTYPE_ARRAY: |
| case ARGTYPE_STRUCT: |
| case ARGTYPE_POINTER: |
| assert(!"Not a simple type!"); |
| }; |
| abort(); |
| } |
| |
| struct arg_type_info * |
| type_get_voidptr(void) |
| { |
| struct arg_type_info *void_info = type_get_simple(ARGTYPE_VOID); |
| static struct arg_type_info *ret; |
| if (ret == NULL) { |
| static struct arg_type_info ptr_info; |
| type_init_pointer(&ptr_info, void_info, 0); |
| ret = &ptr_info; |
| } |
| return ret; |
| } |
| |
| static void |
| type_init_common(struct arg_type_info *info, enum arg_type type) |
| { |
| info->type = type; |
| info->lens = NULL; |
| info->own_lens = 0; |
| } |
| |
| struct struct_field { |
| struct arg_type_info *info; |
| int own_info; |
| }; |
| |
| void |
| type_init_struct(struct arg_type_info *info) |
| { |
| type_init_common(info, ARGTYPE_STRUCT); |
| VECT_INIT(&info->u.entries, struct struct_field); |
| } |
| |
| int |
| type_struct_add(struct arg_type_info *info, |
| struct arg_type_info *field_info, int own) |
| { |
| assert(info->type == ARGTYPE_STRUCT); |
| struct struct_field field = { field_info, own }; |
| return VECT_PUSHBACK(&info->u.entries, &field); |
| } |
| |
| struct arg_type_info * |
| type_struct_get(struct arg_type_info *info, size_t idx) |
| { |
| assert(info->type == ARGTYPE_STRUCT); |
| return VECT_ELEMENT(&info->u.entries, struct struct_field, idx)->info; |
| } |
| |
| size_t |
| type_struct_size(struct arg_type_info *info) |
| { |
| assert(info->type == ARGTYPE_STRUCT); |
| return vect_size(&info->u.entries); |
| } |
| |
| static void |
| struct_field_dtor(struct struct_field *field, void *data) |
| { |
| if (field->own_info) { |
| type_destroy(field->info); |
| free(field->info); |
| } |
| } |
| |
| static void |
| type_struct_destroy(struct arg_type_info *info) |
| { |
| VECT_DESTROY(&info->u.entries, struct struct_field, |
| struct_field_dtor, NULL); |
| } |
| |
| static int |
| layout_struct(struct process *proc, struct arg_type_info *info, |
| size_t *sizep, size_t *alignmentp, size_t *offsetofp) |
| { |
| size_t sz = 0; |
| size_t max_alignment = 0; |
| size_t i; |
| size_t offsetof_field = (size_t)-1; |
| if (offsetofp != NULL) |
| offsetof_field = *offsetofp; |
| |
| assert(info->type == ARGTYPE_STRUCT); |
| for (i = 0; i < vect_size(&info->u.entries); ++i) { |
| struct struct_field *field |
| = VECT_ELEMENT(&info->u.entries, |
| struct struct_field, i); |
| |
| size_t alignment = type_alignof(proc, field->info); |
| if (alignment == (size_t)-1) |
| return -1; |
| |
| /* Add padding to SZ to align the next element. */ |
| sz = align(sz, alignment); |
| if (i == offsetof_field) { |
| *offsetofp = sz; |
| if (sizep == NULL && alignmentp == NULL) |
| return 0; |
| } |
| |
| size_t size = type_sizeof(proc, field->info); |
| if (size == (size_t)-1) |
| return -1; |
| sz += size; |
| |
| if (alignment > max_alignment) |
| max_alignment = alignment; |
| } |
| |
| if (max_alignment > 0) |
| sz = align(sz, max_alignment); |
| |
| if (sizep != NULL) |
| *sizep = sz; |
| |
| if (alignmentp != NULL) |
| *alignmentp = max_alignment; |
| |
| return 0; |
| } |
| |
| void |
| type_init_array(struct arg_type_info *info, |
| struct arg_type_info *element_info, int own_info, |
| struct expr_node *length_expr, int own_length) |
| { |
| type_init_common(info, ARGTYPE_ARRAY); |
| info->u.array_info.elt_type = element_info; |
| info->u.array_info.own_info = own_info; |
| info->u.array_info.length = length_expr; |
| info->u.array_info.own_length = own_length; |
| } |
| |
| static void |
| type_array_destroy(struct arg_type_info *info) |
| { |
| if (info->u.array_info.own_info) { |
| type_destroy(info->u.array_info.elt_type); |
| free(info->u.array_info.elt_type); |
| } |
| if (info->u.array_info.own_length) { |
| expr_destroy(info->u.array_info.length); |
| free(info->u.array_info.length); |
| } |
| } |
| |
| void |
| type_init_pointer(struct arg_type_info *info, |
| struct arg_type_info *pointee_info, int own_info) |
| { |
| type_init_common(info, ARGTYPE_POINTER); |
| info->u.ptr_info.info = pointee_info; |
| info->u.ptr_info.own_info = own_info; |
| } |
| |
| static void |
| type_pointer_destroy(struct arg_type_info *info) |
| { |
| if (info->u.ptr_info.own_info) { |
| type_destroy(info->u.ptr_info.info); |
| free(info->u.ptr_info.info); |
| } |
| } |
| |
| void |
| type_destroy(struct arg_type_info *info) |
| { |
| if (info == NULL) |
| return; |
| |
| switch (info->type) { |
| case ARGTYPE_STRUCT: |
| type_struct_destroy(info); |
| break; |
| |
| case ARGTYPE_ARRAY: |
| type_array_destroy(info); |
| break; |
| |
| case ARGTYPE_POINTER: |
| type_pointer_destroy(info); |
| break; |
| |
| case ARGTYPE_VOID: |
| case ARGTYPE_INT: |
| case ARGTYPE_UINT: |
| case ARGTYPE_LONG: |
| case ARGTYPE_ULONG: |
| case ARGTYPE_CHAR: |
| case ARGTYPE_SHORT: |
| case ARGTYPE_USHORT: |
| case ARGTYPE_FLOAT: |
| case ARGTYPE_DOUBLE: |
| break; |
| } |
| |
| if (info->own_lens) { |
| lens_destroy(info->lens); |
| free(info->lens); |
| } |
| } |
| |
| static int |
| type_alloc_and_clone(struct arg_type_info **retpp, |
| struct arg_type_info *info, int own) |
| { |
| *retpp = info; |
| if (own) { |
| *retpp = malloc(sizeof **retpp); |
| if (*retpp == NULL || type_clone(*retpp, info) < 0) { |
| free(*retpp); |
| return -1; |
| } |
| } |
| return 0; |
| } |
| |
| static enum callback_status |
| clone_struct_add_field(const struct struct_field *field, void *data) |
| { |
| struct arg_type_info *retp = data; |
| struct arg_type_info *info; |
| if (type_alloc_and_clone(&info, field->info, field->own_info) < 0) { |
| fail: |
| if (info != field->info) |
| free(info); |
| return CBS_STOP; |
| } |
| |
| if (type_struct_add(retp, info, field->own_info) < 0) { |
| if (field->own_info) |
| type_destroy(info); |
| goto fail; |
| } |
| |
| return CBS_CONT; |
| } |
| |
| int |
| type_clone(struct arg_type_info *retp, const struct arg_type_info *info) |
| { |
| switch (info->type) { |
| case ARGTYPE_STRUCT: |
| type_init_struct(retp); |
| if (VECT_EACH_CST(&info->u.entries, struct struct_field, NULL, |
| clone_struct_add_field, retp) != NULL) { |
| type_destroy(retp); |
| return -1; |
| } |
| break; |
| |
| case ARGTYPE_ARRAY:; |
| struct arg_type_info *elt_type; |
| if (type_alloc_and_clone(&elt_type, info->u.array_info.elt_type, |
| info->u.array_info.own_info) < 0) |
| return -1; |
| |
| assert(!info->u.array_info.own_length); // XXXXXXX |
| type_init_array(retp, elt_type, info->u.array_info.own_info, |
| info->u.array_info.length, |
| info->u.array_info.own_length); |
| break; |
| |
| case ARGTYPE_POINTER:; |
| struct arg_type_info *ninfo; |
| if (type_alloc_and_clone(&ninfo, info->u.ptr_info.info, |
| info->u.ptr_info.own_info) < 0) |
| return -1; |
| type_init_pointer(retp, ninfo, info->u.ptr_info.own_info); |
| break; |
| |
| case ARGTYPE_VOID: |
| case ARGTYPE_INT: |
| case ARGTYPE_UINT: |
| case ARGTYPE_LONG: |
| case ARGTYPE_ULONG: |
| case ARGTYPE_CHAR: |
| case ARGTYPE_SHORT: |
| case ARGTYPE_USHORT: |
| case ARGTYPE_FLOAT: |
| case ARGTYPE_DOUBLE: |
| *retp = *info; |
| break; |
| } |
| |
| assert(!info->own_lens); |
| retp->lens = info->lens; |
| retp->own_lens = info->own_lens; |
| return 0; |
| } |
| |
| #ifdef ARCH_HAVE_SIZEOF |
| size_t arch_type_sizeof(struct process *proc, struct arg_type_info *arg); |
| #else |
| size_t |
| arch_type_sizeof(struct process *proc, struct arg_type_info *arg) |
| { |
| /* Use default value. */ |
| return (size_t)-2; |
| } |
| #endif |
| |
| #ifdef ARCH_HAVE_ALIGNOF |
| size_t arch_type_alignof(struct process *proc, struct arg_type_info *arg); |
| #else |
| size_t |
| arch_type_alignof(struct process *proc, struct arg_type_info *arg) |
| { |
| /* Use default value. */ |
| return (size_t)-2; |
| } |
| #endif |
| |
| /* We need to support alignments that are not power of two. E.g. long |
| * double on x86 has alignment of 12. */ |
| size_t |
| align(size_t sz, size_t alignment) |
| { |
| assert(alignment != 0); |
| |
| if ((sz % alignment) != 0) |
| sz = ((sz / alignment) + 1) * alignment; |
| |
| return sz; |
| } |
| |
| size_t |
| type_sizeof(struct process *proc, struct arg_type_info *type) |
| { |
| size_t arch_size = arch_type_sizeof(proc, type); |
| if (arch_size != (size_t)-2) |
| return arch_size; |
| |
| switch (type->type) { |
| size_t size; |
| case ARGTYPE_CHAR: |
| return sizeof(char); |
| |
| case ARGTYPE_SHORT: |
| case ARGTYPE_USHORT: |
| return sizeof(short); |
| |
| case ARGTYPE_INT: |
| case ARGTYPE_UINT: |
| return sizeof(int); |
| |
| case ARGTYPE_LONG: |
| case ARGTYPE_ULONG: |
| return sizeof(long); |
| |
| case ARGTYPE_FLOAT: |
| return sizeof(float); |
| |
| case ARGTYPE_DOUBLE: |
| return sizeof(double); |
| |
| case ARGTYPE_STRUCT: |
| if (layout_struct(proc, type, &size, NULL, NULL) < 0) |
| return (size_t)-1; |
| return size; |
| |
| case ARGTYPE_POINTER: |
| return sizeof(void *); |
| |
| case ARGTYPE_ARRAY: |
| if (expr_is_compile_constant(type->u.array_info.length)) { |
| long l; |
| if (expr_eval_constant(type->u.array_info.length, |
| &l) < 0) |
| return -1; |
| |
| struct arg_type_info *elt_ti |
| = type->u.array_info.elt_type; |
| |
| size_t elt_size = type_sizeof(proc, elt_ti); |
| if (elt_size == (size_t)-1) |
| return (size_t)-1; |
| |
| return ((size_t)l) * elt_size; |
| |
| } else { |
| /* Flexible arrays don't count into the |
| * sizeof. */ |
| return 0; |
| } |
| |
| case ARGTYPE_VOID: |
| return 0; |
| } |
| |
| abort(); |
| } |
| |
| #undef alignof |
| #define alignof(field,st) ((size_t) ((char*) &st.field - (char*) &st)) |
| |
| size_t |
| type_alignof(struct process *proc, struct arg_type_info *type) |
| { |
| size_t arch_alignment = arch_type_alignof(proc, type); |
| if (arch_alignment != (size_t)-2) |
| return arch_alignment; |
| |
| struct { char c; char C; } cC; |
| struct { char c; short s; } cs; |
| struct { char c; int i; } ci; |
| struct { char c; long l; } cl; |
| struct { char c; void* p; } cp; |
| struct { char c; float f; } cf; |
| struct { char c; double d; } cd; |
| |
| static size_t char_alignment = alignof(C, cC); |
| static size_t short_alignment = alignof(s, cs); |
| static size_t int_alignment = alignof(i, ci); |
| static size_t long_alignment = alignof(l, cl); |
| static size_t ptr_alignment = alignof(p, cp); |
| static size_t float_alignment = alignof(f, cf); |
| static size_t double_alignment = alignof(d, cd); |
| |
| switch (type->type) { |
| size_t alignment; |
| case ARGTYPE_LONG: |
| case ARGTYPE_ULONG: |
| return long_alignment; |
| case ARGTYPE_CHAR: |
| return char_alignment; |
| case ARGTYPE_SHORT: |
| case ARGTYPE_USHORT: |
| return short_alignment; |
| case ARGTYPE_FLOAT: |
| return float_alignment; |
| case ARGTYPE_DOUBLE: |
| return double_alignment; |
| case ARGTYPE_POINTER: |
| return ptr_alignment; |
| |
| case ARGTYPE_ARRAY: |
| return type_alignof(proc, type->u.array_info.elt_type); |
| |
| case ARGTYPE_STRUCT: |
| if (layout_struct(proc, type, NULL, &alignment, NULL) < 0) |
| return (size_t)-1; |
| return alignment; |
| |
| default: |
| return int_alignment; |
| } |
| } |
| |
| size_t |
| type_offsetof(struct process *proc, struct arg_type_info *type, size_t emt) |
| { |
| assert(type->type == ARGTYPE_STRUCT |
| || type->type == ARGTYPE_ARRAY); |
| |
| switch (type->type) { |
| size_t alignment; |
| size_t size; |
| case ARGTYPE_ARRAY: |
| alignment = type_alignof(proc, type->u.array_info.elt_type); |
| if (alignment == (size_t)-1) |
| return (size_t)-1; |
| |
| size = type_sizeof(proc, type->u.array_info.elt_type); |
| if (size == (size_t)-1) |
| return (size_t)-1; |
| |
| return emt * align(size, alignment); |
| |
| case ARGTYPE_STRUCT: |
| if (layout_struct(proc, type, NULL, NULL, &emt) < 0) |
| return (size_t)-1; |
| return emt; |
| |
| default: |
| abort(); |
| } |
| } |
| |
| struct arg_type_info * |
| type_element(struct arg_type_info *info, size_t emt) |
| { |
| assert(info->type == ARGTYPE_STRUCT |
| || info->type == ARGTYPE_ARRAY); |
| |
| switch (info->type) { |
| case ARGTYPE_ARRAY: |
| return info->u.array_info.elt_type; |
| |
| case ARGTYPE_STRUCT: |
| assert(emt < type_struct_size(info)); |
| return type_struct_get(info, emt); |
| |
| default: |
| abort(); |
| } |
| } |
| |
| size_t |
| type_aggregate_size(struct arg_type_info *info) |
| { |
| assert(info->type == ARGTYPE_STRUCT |
| || info->type == ARGTYPE_ARRAY); |
| |
| switch (info->type) { |
| long ret; |
| case ARGTYPE_ARRAY: |
| if (expr_eval_constant(info->u.array_info.length, &ret) < 0) |
| return (size_t)-1; |
| return (size_t)ret; |
| |
| case ARGTYPE_STRUCT: |
| return type_struct_size(info); |
| |
| default: |
| abort(); |
| } |
| } |
| |
| int |
| type_is_integral(enum arg_type type) |
| { |
| switch (type) { |
| case ARGTYPE_INT: |
| case ARGTYPE_UINT: |
| case ARGTYPE_LONG: |
| case ARGTYPE_ULONG: |
| case ARGTYPE_CHAR: |
| case ARGTYPE_SHORT: |
| case ARGTYPE_USHORT: |
| return 1; |
| |
| case ARGTYPE_VOID: |
| case ARGTYPE_FLOAT: |
| case ARGTYPE_DOUBLE: |
| case ARGTYPE_ARRAY: |
| case ARGTYPE_STRUCT: |
| case ARGTYPE_POINTER: |
| return 0; |
| } |
| abort(); |
| } |
| |
| int |
| type_is_signed(enum arg_type type) |
| { |
| assert(type_is_integral(type)); |
| |
| switch (type) { |
| case ARGTYPE_CHAR: |
| return CHAR_MIN != 0; |
| |
| case ARGTYPE_SHORT: |
| case ARGTYPE_INT: |
| case ARGTYPE_LONG: |
| return 1; |
| |
| case ARGTYPE_UINT: |
| case ARGTYPE_ULONG: |
| case ARGTYPE_USHORT: |
| return 0; |
| |
| case ARGTYPE_VOID: |
| case ARGTYPE_FLOAT: |
| case ARGTYPE_DOUBLE: |
| case ARGTYPE_ARRAY: |
| case ARGTYPE_STRUCT: |
| case ARGTYPE_POINTER: |
| abort(); |
| } |
| abort(); |
| } |
| |
| struct arg_type_info * |
| type_get_fp_equivalent(struct arg_type_info *info) |
| { |
| /* Extract innermost structure. Give up early if any |
| * component has more than one element. */ |
| while (info->type == ARGTYPE_STRUCT) { |
| if (type_struct_size(info) != 1) |
| return NULL; |
| info = type_element(info, 0); |
| } |
| |
| switch (info->type) { |
| case ARGTYPE_CHAR: |
| case ARGTYPE_SHORT: |
| case ARGTYPE_INT: |
| case ARGTYPE_LONG: |
| case ARGTYPE_UINT: |
| case ARGTYPE_ULONG: |
| case ARGTYPE_USHORT: |
| case ARGTYPE_VOID: |
| case ARGTYPE_ARRAY: |
| case ARGTYPE_POINTER: |
| return NULL; |
| |
| case ARGTYPE_FLOAT: |
| case ARGTYPE_DOUBLE: |
| return info; |
| |
| case ARGTYPE_STRUCT: |
| abort(); |
| } |
| abort(); |
| } |
| |
| struct arg_type_info * |
| type_get_hfa_type(struct arg_type_info *info, size_t *countp) |
| { |
| assert(info != NULL); |
| if (info->type != ARGTYPE_STRUCT |
| && info->type != ARGTYPE_ARRAY) |
| return NULL; |
| |
| size_t n = type_aggregate_size(info); |
| if (n == (size_t)-1) |
| return NULL; |
| |
| struct arg_type_info *ret = NULL; |
| *countp = 0; |
| |
| while (n-- > 0) { |
| struct arg_type_info *emt = type_element(info, n); |
| |
| size_t emt_count = 1; |
| if (emt->type == ARGTYPE_STRUCT || emt->type == ARGTYPE_ARRAY) |
| emt = type_get_hfa_type(emt, &emt_count); |
| if (emt == NULL) |
| return NULL; |
| if (ret == NULL) { |
| if (emt->type != ARGTYPE_FLOAT |
| && emt->type != ARGTYPE_DOUBLE) |
| return NULL; |
| ret = emt; |
| } |
| if (emt->type != ret->type) |
| return NULL; |
| *countp += emt_count; |
| } |
| return ret; |
| } |