1 /* Common parts for printing diff output */ 2 /* 3 * Copyright (c) 2020 Neels Hofmeyr <neels@hofmeyr.de> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18 #include <ctype.h> 19 #include <errno.h> 20 #include <stdbool.h> 21 #include <stdint.h> 22 #include <stdio.h> 23 #include <stdlib.h> 24 #include <string.h> 25 #include <unistd.h> 26 27 #include <arraylist.h> 28 #include <diff_main.h> 29 #include <diff_output.h> 30 31 #include "diff_internal.h" 32 33 static int 34 get_atom_byte(int *ch, struct diff_atom *atom, off_t off) 35 { 36 off_t cur; 37 38 if (atom->at != NULL) { 39 *ch = atom->at[off]; 40 return 0; 41 } 42 43 cur = ftello(atom->root->f); 44 if (cur == -1) 45 return errno; 46 47 if (cur != atom->pos + off && 48 fseeko(atom->root->f, atom->pos + off, SEEK_SET) == -1) 49 return errno; 50 51 *ch = fgetc(atom->root->f); 52 if (*ch == EOF && ferror(atom->root->f)) 53 return errno; 54 55 return 0; 56 } 57 58 #define DIFF_OUTPUT_BUF_SIZE 512 59 60 int 61 diff_output_lines(struct diff_output_info *outinfo, FILE *dest, 62 const char *prefix, struct diff_atom *start_atom, 63 unsigned int count) 64 { 65 struct diff_atom *atom; 66 off_t outoff = 0, *offp; 67 uint8_t *typep; 68 int rc; 69 70 if (outinfo && outinfo->line_offsets.len > 0) { 71 unsigned int idx = outinfo->line_offsets.len - 1; 72 outoff = outinfo->line_offsets.head[idx]; 73 } 74 75 foreach_diff_atom(atom, start_atom, count) { 76 off_t outlen = 0; 77 int i, ch, nbuf = 0; 78 unsigned int len = atom->len; 79 unsigned char buf[DIFF_OUTPUT_BUF_SIZE + 1 /* '\n' */]; 80 size_t n; 81 82 n = strlcpy(buf, prefix, sizeof(buf)); 83 if (n >= DIFF_OUTPUT_BUF_SIZE) /* leave room for '\n' */ 84 return ENOBUFS; 85 nbuf += n; 86 87 if (len) { 88 rc = get_atom_byte(&ch, atom, len - 1); 89 if (rc) 90 return rc; 91 if (ch == '\n') 92 len--; 93 } 94 95 for (i = 0; i < len; i++) { 96 rc = get_atom_byte(&ch, atom, i); 97 if (rc) 98 return rc; 99 if (nbuf >= DIFF_OUTPUT_BUF_SIZE) { 100 rc = fwrite(buf, 1, nbuf, dest); 101 if (rc != nbuf) 102 return errno; 103 outlen += rc; 104 nbuf = 0; 105 } 106 buf[nbuf++] = ch; 107 } 108 buf[nbuf++] = '\n'; 109 rc = fwrite(buf, 1, nbuf, dest); 110 if (rc != nbuf) 111 return errno; 112 outlen += rc; 113 if (outinfo) { 114 ARRAYLIST_ADD(offp, outinfo->line_offsets); 115 if (offp == NULL) 116 return ENOMEM; 117 outoff += outlen; 118 *offp = outoff; 119 ARRAYLIST_ADD(typep, outinfo->line_types); 120 if (typep == NULL) 121 return ENOMEM; 122 *typep = *prefix == ' ' ? DIFF_LINE_CONTEXT : 123 *prefix == '-' ? DIFF_LINE_MINUS : 124 *prefix == '+' ? DIFF_LINE_PLUS : DIFF_LINE_NONE; 125 } 126 } 127 128 return DIFF_RC_OK; 129 } 130 131 int 132 diff_output_chunk_left_version(struct diff_output_info **output_info, 133 FILE *dest, 134 const struct diff_input_info *info, 135 const struct diff_result *result, 136 const struct diff_chunk_context *cc) 137 { 138 int rc, c_idx; 139 struct diff_output_info *outinfo = NULL; 140 141 if (diff_range_empty(&cc->left)) 142 return DIFF_RC_OK; 143 144 if (output_info) { 145 *output_info = diff_output_info_alloc(); 146 if (*output_info == NULL) 147 return ENOMEM; 148 outinfo = *output_info; 149 } 150 151 /* Write out all chunks on the left side. */ 152 for (c_idx = cc->chunk.start; c_idx < cc->chunk.end; c_idx++) { 153 const struct diff_chunk *c = &result->chunks.head[c_idx]; 154 155 if (c->left_count) { 156 rc = diff_output_lines(outinfo, dest, "", 157 c->left_start, c->left_count); 158 if (rc) 159 return rc; 160 } 161 } 162 163 return DIFF_RC_OK; 164 } 165 166 int 167 diff_output_chunk_right_version(struct diff_output_info **output_info, 168 FILE *dest, 169 const struct diff_input_info *info, 170 const struct diff_result *result, 171 const struct diff_chunk_context *cc) 172 { 173 int rc, c_idx; 174 struct diff_output_info *outinfo = NULL; 175 176 if (diff_range_empty(&cc->right)) 177 return DIFF_RC_OK; 178 179 if (output_info) { 180 *output_info = diff_output_info_alloc(); 181 if (*output_info == NULL) 182 return ENOMEM; 183 outinfo = *output_info; 184 } 185 186 /* Write out all chunks on the right side. */ 187 for (c_idx = cc->chunk.start; c_idx < cc->chunk.end; c_idx++) { 188 const struct diff_chunk *c = &result->chunks.head[c_idx]; 189 190 if (c->right_count) { 191 rc = diff_output_lines(outinfo, dest, "", c->right_start, 192 c->right_count); 193 if (rc) 194 return rc; 195 } 196 } 197 198 return DIFF_RC_OK; 199 } 200 201 int 202 diff_output_trailing_newline_msg(struct diff_output_info *outinfo, FILE *dest, 203 const struct diff_chunk *c) 204 { 205 enum diff_chunk_type chunk_type = diff_chunk_type(c); 206 struct diff_atom *atom, *start_atom; 207 unsigned int atom_count; 208 int rc, ch; 209 off_t outoff = 0, *offp; 210 uint8_t *typep; 211 212 213 if (chunk_type == CHUNK_MINUS || chunk_type == CHUNK_SAME) { 214 start_atom = c->left_start; 215 atom_count = c->left_count; 216 } else if (chunk_type == CHUNK_PLUS) { 217 start_atom = c->right_start; 218 atom_count = c->right_count; 219 } else 220 return EINVAL; 221 222 /* Locate the last atom. */ 223 if (atom_count == 0) 224 return EINVAL; 225 atom = &start_atom[atom_count - 1]; 226 227 rc = get_atom_byte(&ch, atom, atom->len - 1); 228 if (rc != DIFF_RC_OK) 229 return rc; 230 231 if (ch != '\n') { 232 if (outinfo && outinfo->line_offsets.len > 0) { 233 unsigned int idx = outinfo->line_offsets.len - 1; 234 outoff = outinfo->line_offsets.head[idx]; 235 } 236 rc = fprintf(dest, "\\ No newline at end of file\n"); 237 if (rc < 0) 238 return errno; 239 if (outinfo) { 240 ARRAYLIST_ADD(offp, outinfo->line_offsets); 241 if (offp == NULL) 242 return ENOMEM; 243 outoff += rc; 244 *offp = outoff; 245 ARRAYLIST_ADD(typep, outinfo->line_types); 246 if (typep == NULL) 247 return ENOMEM; 248 *typep = DIFF_LINE_NONE; 249 } 250 } 251 252 return DIFF_RC_OK; 253 } 254 255 static bool 256 is_function_prototype(unsigned char ch) 257 { 258 return (isalpha((unsigned char)ch) || ch == '_' || ch == '$'); 259 } 260 261 #define begins_with(s, pre) (strncmp(s, pre, sizeof(pre)-1) == 0) 262 263 int 264 diff_output_match_function_prototype(char *prototype, size_t prototype_size, 265 int *last_prototype_idx, const struct diff_result *result, 266 const struct diff_chunk_context *cc) 267 { 268 struct diff_atom *start_atom, *atom; 269 const struct diff_data *data; 270 unsigned char buf[DIFF_FUNCTION_CONTEXT_SIZE]; 271 const char *state = NULL; 272 int rc, i, ch; 273 274 if (result->left->atoms.len > 0 && cc->left.start > 0) { 275 data = result->left; 276 start_atom = &data->atoms.head[cc->left.start - 1]; 277 } else 278 return DIFF_RC_OK; 279 280 diff_data_foreach_atom_backwards_from(start_atom, atom, data) { 281 int atom_idx = diff_atom_root_idx(data, atom); 282 if (atom_idx < *last_prototype_idx) 283 break; 284 rc = get_atom_byte(&ch, atom, 0); 285 if (rc) 286 return rc; 287 buf[0] = (unsigned char)ch; 288 if (!is_function_prototype(buf[0])) 289 continue; 290 for (i = 1; i < atom->len && i < sizeof(buf) - 1; i++) { 291 rc = get_atom_byte(&ch, atom, i); 292 if (rc) 293 return rc; 294 if (ch == '\n') 295 break; 296 buf[i] = (unsigned char)ch; 297 } 298 buf[i] = '\0'; 299 if (begins_with(buf, "private:")) { 300 if (!state) 301 state = " (private)"; 302 } else if (begins_with(buf, "protected:")) { 303 if (!state) 304 state = " (protected)"; 305 } else if (begins_with(buf, "public:")) { 306 if (!state) 307 state = " (public)"; 308 } else { 309 if (state) /* don't care about truncation */ 310 strlcat(buf, state, sizeof(buf)); 311 strlcpy(prototype, buf, prototype_size); 312 break; 313 } 314 } 315 316 *last_prototype_idx = diff_atom_root_idx(data, start_atom); 317 return DIFF_RC_OK; 318 } 319 320 struct diff_output_info * 321 diff_output_info_alloc(void) 322 { 323 struct diff_output_info *output_info; 324 off_t *offp; 325 uint8_t *typep; 326 327 output_info = malloc(sizeof(*output_info)); 328 if (output_info != NULL) { 329 ARRAYLIST_INIT(output_info->line_offsets, 128); 330 ARRAYLIST_ADD(offp, output_info->line_offsets); 331 if (offp == NULL) { 332 diff_output_info_free(output_info); 333 return NULL; 334 } 335 *offp = 0; 336 ARRAYLIST_INIT(output_info->line_types, 128); 337 ARRAYLIST_ADD(typep, output_info->line_types); 338 if (typep == NULL) { 339 diff_output_info_free(output_info); 340 return NULL; 341 } 342 *typep = DIFF_LINE_NONE; 343 } 344 return output_info; 345 } 346 347 void 348 diff_output_info_free(struct diff_output_info *output_info) 349 { 350 ARRAYLIST_FREE(output_info->line_offsets); 351 ARRAYLIST_FREE(output_info->line_types); 352 free(output_info); 353 } 354 355 const char * 356 diff_output_get_label_left(const struct diff_input_info *info) 357 { 358 if (info->flags & DIFF_INPUT_LEFT_NONEXISTENT) 359 return "/dev/null"; 360 361 return info->left_path ? info->left_path : "a"; 362 } 363 364 const char * 365 diff_output_get_label_right(const struct diff_input_info *info) 366 { 367 if (info->flags & DIFF_INPUT_RIGHT_NONEXISTENT) 368 return "/dev/null"; 369 370 return info->right_path ? info->right_path : "b"; 371 } 372