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 ch == '-' || ch == '+'); 260 } 261 262 #define begins_with(s, pre) (strncmp(s, pre, sizeof(pre)-1) == 0) 263 264 int 265 diff_output_match_function_prototype(char *prototype, size_t prototype_size, 266 int *last_prototype_idx, const struct diff_result *result, 267 int chunk_start_line) 268 { 269 struct diff_atom *start_atom, *atom; 270 const struct diff_data *data; 271 unsigned char buf[DIFF_FUNCTION_CONTEXT_SIZE]; 272 const char *state = NULL; 273 int rc, i, ch; 274 275 if (result->left->atoms.len > 0 && chunk_start_line > 0) { 276 data = result->left; 277 start_atom = &data->atoms.head[chunk_start_line - 1]; 278 } else 279 return DIFF_RC_OK; 280 281 diff_data_foreach_atom_backwards_from(start_atom, atom, data) { 282 int atom_idx = diff_atom_root_idx(data, atom); 283 if (atom_idx < *last_prototype_idx) 284 break; 285 rc = get_atom_byte(&ch, atom, 0); 286 if (rc) 287 return rc; 288 buf[0] = (unsigned char)ch; 289 if (!is_function_prototype(buf[0])) 290 continue; 291 for (i = 1; i < atom->len && i < sizeof(buf) - 1; i++) { 292 rc = get_atom_byte(&ch, atom, i); 293 if (rc) 294 return rc; 295 if (ch == '\n') 296 break; 297 buf[i] = (unsigned char)ch; 298 } 299 buf[i] = '\0'; 300 if (begins_with(buf, "private:")) { 301 if (!state) 302 state = " (private)"; 303 } else if (begins_with(buf, "protected:")) { 304 if (!state) 305 state = " (protected)"; 306 } else if (begins_with(buf, "public:")) { 307 if (!state) 308 state = " (public)"; 309 } else { 310 if (state) /* don't care about truncation */ 311 strlcat(buf, state, sizeof(buf)); 312 strlcpy(prototype, buf, prototype_size); 313 break; 314 } 315 } 316 317 *last_prototype_idx = diff_atom_root_idx(data, start_atom); 318 return DIFF_RC_OK; 319 } 320 321 struct diff_output_info * 322 diff_output_info_alloc(void) 323 { 324 struct diff_output_info *output_info; 325 off_t *offp; 326 uint8_t *typep; 327 328 output_info = malloc(sizeof(*output_info)); 329 if (output_info != NULL) { 330 ARRAYLIST_INIT(output_info->line_offsets, 128); 331 ARRAYLIST_ADD(offp, output_info->line_offsets); 332 if (offp == NULL) { 333 diff_output_info_free(output_info); 334 return NULL; 335 } 336 *offp = 0; 337 ARRAYLIST_INIT(output_info->line_types, 128); 338 ARRAYLIST_ADD(typep, output_info->line_types); 339 if (typep == NULL) { 340 diff_output_info_free(output_info); 341 return NULL; 342 } 343 *typep = DIFF_LINE_NONE; 344 } 345 return output_info; 346 } 347 348 void 349 diff_output_info_free(struct diff_output_info *output_info) 350 { 351 ARRAYLIST_FREE(output_info->line_offsets); 352 ARRAYLIST_FREE(output_info->line_types); 353 free(output_info); 354 } 355 356 const char * 357 diff_output_get_label_left(const struct diff_input_info *info) 358 { 359 if (info->flags & DIFF_INPUT_LEFT_NONEXISTENT) 360 return "/dev/null"; 361 362 return info->left_path ? info->left_path : "a"; 363 } 364 365 const char * 366 diff_output_get_label_right(const struct diff_input_info *info) 367 { 368 if (info->flags & DIFF_INPUT_RIGHT_NONEXISTENT) 369 return "/dev/null"; 370 371 return info->right_path ? info->right_path : "b"; 372 } 373