Don't self-parameterize Tast_visitor classes
[hiphop-php.git] / hphp / hack / src / hhbc / semdiff / diff.ml
blobdcaa2b7a2ef50c48f8f44e6c71eaec0c5ac389d0
1 (**
2 * Copyright (c) 2017, Facebook, Inc.
3 * All rights reserved.
5 * This source code is licensed under the MIT license found in the
6 * LICENSE file in the "hack" directory of this source tree.
8 *)
10 (* TODO: change this over to Hh_core (and replace silly option functions)*)
12 module EA = Emit_adata
13 module Log = Semdiff_logging
14 module Utils = Semdiff_utils
16 let concatstrs = String.concat ""
18 let substedit s1 s2 = [Log.Del s1; Log.Add s2]
20 (* TODO: replace list with tree-type thingy for concatenation efficiency *)
21 type edit_sequence = Log.tagged_string list
23 let mymin (a,va) (b,vb) = if a<b then (a,va) else (b,vb)
24 let keys_of al = List.map fst al
25 let difference l1 l2 = List.filter (fun k -> not (List.mem k l2)) l1
26 let intersect l1 l2 = List.filter (fun k -> List.mem k l2) l1
28 let sumsize elt_size l = List.fold_left (fun n e -> n + elt_size e) 0 l
30 type 'a compare = {
31 comparer : 'a -> 'a -> int * (int * edit_sequence);
32 size_of : 'a -> int;
33 string_of : 'a -> string
36 (* Dynamic programming implementation of Levenshtein distance for lists
37 TODO: reduce memory usage by only memoizing last few rows of the table
38 (but depends what I do for larger pattern matches)
39 TODO: amalgamate contiguous sequences of additions/deletions into additions/
40 deletions of sequences - that looks much better in the output
42 type action =
43 | Delete
44 | Insert
45 | Subedit of edit_sequence (* TODO: something more structured? *)
47 let levenshtein value_comparer l1 l2 =
48 let a1 = Array.of_list l1 in
49 let a2 = Array.of_list l2 in
50 let len1 = Array.length a1 in
51 let len2 = Array.length a2 in
52 let memo = Array.make_matrix (len1+1) (len2+1) (0,Insert) in (* dummy *)
53 let rec readback i j sofar =
54 if i = 0 && j=0 then sofar
55 else
56 match memo.(i).(j) with
57 | (_,Subedit edits) -> readback (i-1) (j-1) (edits @ sofar)
58 | (_,Insert) ->
59 readback i (j-1) ([Log.Add (value_comparer.string_of a2.(j-1))] @ sofar)
60 | (_,Delete) ->
61 readback (i-1) j ([Log.Del (value_comparer.string_of a1.(i-1))] @ sofar) in
62 for j = 1 to len2 do
63 memo.(0).(j) <- let (sizesofar,_) = memo.(0).(j-1) in
64 (sizesofar + value_comparer.size_of a2.(j-1), Insert)
65 done;
66 for i = 1 to len1 do
67 memo.(i).(0) <- (let (sizesofar,_) = memo.(i-1).(0) in
68 (sizesofar + value_comparer.size_of a1.(i-1), Delete));
69 for j = 1 to len2 do
70 let (c,_) = memo.(i-1).(j-1) in
71 let dosubedit =
72 let (editcost,(_size,edits)) =
73 value_comparer.comparer a1.(i-1) a2.(j-1) in
74 (c+editcost, Subedit edits) in
75 let (d,_) = memo.(i-1).(j) in
76 let dodel = (d+value_comparer.size_of a1.(i-1), Delete) in
77 let (ins,_) = memo.(i).(j-1) in
78 let doins = (ins+value_comparer.size_of a2.(j-1), Insert) in
79 memo.(i).(j) <- mymin dosubedit (mymin dodel doins)
80 done;
81 done;
82 let (n,_) = memo.(len1).(len2) in
83 let (totalsize,_) = memo.(len1).(0) in
84 (n, (totalsize, readback len1 len2 []))
86 (* In contrast to the above, a really stupid pointwise comparer for lists
87 that runs in linear time and stops producing detailed diff outputs after
88 a threshold has been reached.
89 One could usefully replace this with something better, but still linear.
91 let max_array_size = 1000000
92 let edit_cost_threshold = 20
94 let rec dumb_compare value_comparer l1 l2 costsofar sizesofar edit_list =
95 let possibly_remove x x_xsize =
96 if costsofar < edit_cost_threshold then
97 let new_list = edit_list @ [Log.Del (value_comparer.string_of x)] in
98 if costsofar+x_xsize >= edit_cost_threshold then
99 new_list @ [Log.Def "...truncated..."]
100 else new_list
101 else edit_list in
103 let possibly_add x x_xsize =
104 if costsofar < edit_cost_threshold then
105 let new_list = edit_list @ [Log.Add (value_comparer.string_of x)] in
106 if costsofar+x_xsize >= edit_cost_threshold then
107 new_list @ [Log.Def "...truncated..."]
108 else new_list
109 else edit_list in
111 let possibly_edit edits size =
112 if costsofar < edit_cost_threshold then
113 let new_list = edit_list @ edits in
114 if costsofar + size >= edit_cost_threshold then
115 new_list @ [Log.Def "...truncated..."]
116 else new_list
117 else edit_list in
119 match l1, l2 with
120 | [], [] -> (costsofar, (sizesofar, edit_list))
121 | x::xs, [] -> let x_size = value_comparer.size_of x in
122 dumb_compare value_comparer xs []
123 (costsofar + x_size)
124 (sizesofar + x_size)
125 (possibly_remove x x_size)
126 | [], y::ys -> let y_size = value_comparer.size_of y in
127 dumb_compare value_comparer [] ys
128 (costsofar + y_size)
129 (sizesofar + y_size)
130 (possibly_add y y_size)
131 | x::xs, y::ys ->
132 let (editcost,(size,edits)) = value_comparer.comparer x y in
133 if editcost = 0 then
134 dumb_compare value_comparer xs ys costsofar (size+sizesofar) edit_list
135 else dumb_compare value_comparer xs ys (editcost+costsofar)
136 (size+sizesofar) (possibly_edit edits editcost)
138 let falling_back_list_comparer value_comparer l1 l2 =
139 let len1 = List.length l1 in
140 let len2 = List.length l2 in
141 if len1 * len2 < max_array_size then levenshtein value_comparer l1 l2
142 else (Log.debug (Tty.Normal Tty.Blue) "****Falling back on dumb comparer";
143 dumb_compare value_comparer l1 l2 0 0 [])
145 (* Now the default list comparer, which does levenshtein unless the input looks
146 too big for a quadratic algorithm, when it falls back to dumb_compare
148 let list_comparer elt_comparer inbetween = {
149 comparer = falling_back_list_comparer elt_comparer;
150 size_of = (fun l -> sumsize elt_comparer.size_of l);
151 string_of = (fun l -> "[" ^ (concatstrs
152 (List.map (fun elt -> elt_comparer.string_of elt ^ inbetween) l)) ^ "]");
155 (* Compare string of primitives to account for NaN *)
156 let primitive_comparer to_string = {
157 comparer = (fun n1 n2 ->
158 let n1str = to_string n1 in
159 let n2str = to_string n2 in
160 if n1str=n2str then (0,(1,[]))
161 else (1,(1,substedit n1str n2str)));
162 size_of = (fun _n -> 1);
163 string_of = to_string;
166 let typed_value_to_string v =
167 let buf = Buffer.create 16 in
168 EA.adata_to_buffer buf v;
169 Buffer.contents buf
171 let typed_value_comparer = primitive_comparer typed_value_to_string
173 let int_comparer = primitive_comparer string_of_int
175 let string_comparer = primitive_comparer (fun s -> s)
177 let default_value_text_comparer =
178 let env = Full_fidelity_ast.make_env
179 ~codegen:true
180 ~keep_errors:false Relative_path.default
181 ~fail_open:false
182 ~php5_compat_mode:true in
183 (* remove explicit array keys when its value matches with implicit order *)
184 let remove_explicit_array_keys e =
185 let v = object(self)
186 inherit [_] Ast.endo as super
187 method! on_expr nenv expr =
188 let expr = super#on_expr nenv expr in
189 Ast_constant_folder.fold_expr nenv expr
190 method! on_Array nenv _ values =
191 let values = self#on_list self#on_afield nenv values in
192 let _, values = Hh_core.List.map_env (Some 0L) values (fun exp_i el ->
193 match el, exp_i with
194 | Ast.AFvalue _, Some exp_i -> Some (Int64.add exp_i 1L), el
195 | Ast.AFkvalue ((_, Ast.Int (_, k)), v), _ ->
196 let i_opt =
197 Typed_value.string_to_int_opt
198 ~allow_following:false ~allow_inf:false k in
199 begin match i_opt, exp_i with
200 | Some i, Some exp when i = exp ->
201 Some (Int64.add i 1L), Ast.AFvalue v
202 | Some i, _ ->
203 Some (Int64.add i 1L), el
204 | None, _ ->
205 None, el
207 | _ -> None, el
208 ) in
209 Ast.Array values
210 end in
211 v#on_expr Namespace_env.empty_with_default_popt e in
212 let expr_to_sexp s =
213 let s = Php_escaping.unescape_long_string s in
214 let text = "<?hh\n" ^ "(" ^ s ^ ");" in
215 let text = Full_fidelity_source_text.make Relative_path.default text in
216 let ast = Full_fidelity_ast.from_text env text in
217 match ast.Full_fidelity_ast.ast with
218 | Ast.([Stmt (_, Markup _); Stmt (_, Expr e)]) ->
219 Debug.dump_ast (Ast.AExpr (remove_explicit_array_keys e))
220 | a -> failwith @@
221 "Unexpected shape of default value:" ^ (Debug.dump_ast (Ast.AProgram a)) in
223 comparer = (fun n1 n2 ->
224 if n1 = n2 then (0,(1,[]))
225 else
226 let n1str = expr_to_sexp n1 in
227 let n2str = expr_to_sexp n2 in
228 if n1str = n2str then (0,(1,[]))
229 else (1,(1,substedit n1str n2str)));
230 size_of = (fun _n -> 1);
231 string_of = expr_to_sexp;
233 let bool_comparer = primitive_comparer string_of_bool
236 (* wrap takes a (function a->b), a (function a->string->string), a b-comparer
237 and returns an a-comparer
238 TODO: this is still not quite right, as we want to be able to customize the
239 wrapped edits, and that should be compatible with what we do in string_of.
240 The underlying comparer *should* return a more structured
241 (typed) list of edits with the string_of function being applied later to
242 produce the final output. If I keep going, I guess
243 I'll end up reinventing lenses, or something.
245 let wrap f wrapstring c = {
246 comparer = (fun a1 a2 -> c.comparer (f a1) (f a2));
247 size_of = (fun a -> c.size_of (f a)); (* could adjust these too *)
248 string_of = (fun a -> wrapstring a (c.string_of (f a)))
251 (* add debugging information to a non-empty given edit sequence *)
252 let debug_edit_log_with s e =
253 if e=[] then e else (Log.Def s) :: e
256 (* add debugging information to a given comparer *)
257 let debug_log_with s c = {
258 c with comparer = (fun a a' ->
259 Log.debug (Tty.Normal Tty.Blue) ("comparing " ^ s);
260 let (sz, (d, e)) = c.comparer a a' in
261 (sz, (d, debug_edit_log_with ("for " ^ s ^ ":") e)))
265 (* join combines two, assumed independent, comparers on the same type *)
266 let join combinestrings c1 c2 = {
267 comparer = (fun a b ->
268 let (d1,(s1,e1)) = c1.comparer a b in
269 let (d2,(s2,e2)) = c2.comparer a b in
270 (d1+d2, (s1+s2, e1 @ e2))); (* definitely want better combinations here *)
271 size_of = (fun a -> (c1.size_of a) + (c2.size_of a));
272 string_of = (fun a -> combinestrings (c1.string_of a) (c2.string_of a));
275 let rec joindiffs diffs = match diffs with
276 | [] -> (0,(0,[]))
277 | (d1,(s1,e1)) :: rest -> let (d,(s,e)) = joindiffs rest
278 in (d+d1, (s+s1, e1 @ e))
280 let joinmap f l = joindiffs (List.map f l)
282 (* comparer for sets represented as lists
283 this only works properly for primitive types
284 'cos it uses literal equality
286 let primitive_set_comparer vtostring = {
287 comparer =
288 (fun s1 s2 ->
289 let only1 = List.map (fun v -> Log.Del (vtostring v))
290 (difference s1 s2) in
291 let only2 = List.map (fun v -> Log.Add (vtostring v))
292 (difference s2 s1) in
293 (List.length only1 + List.length only2,
294 (List.length s1, only1 @ only2)));
295 size_of = List.length;
296 string_of =
297 (fun l -> "{" ^ concatstrs (List.map (fun v -> vtostring v ^ " ") l) ^ "}");
300 let option_comparer value_comparer = {
301 comparer = (fun o1 o2 -> match o1, o2 with
302 | None, None -> (0,(1,[]))
303 | Some v1, None -> (value_comparer.size_of v1,
304 (value_comparer.size_of v1,
305 [Log.Del (value_comparer.string_of v1)]))
306 | None, Some v2 ->(value_comparer.size_of v2,
307 (value_comparer.size_of v2,
308 [Log.Add (value_comparer.string_of v2)]))
309 | Some v1, Some v2 -> value_comparer.comparer v1 v2);
310 size_of = (fun o -> match o with | None -> 1
311 | Some v -> 1+(value_comparer.size_of v));
312 string_of = (fun o -> match o with | None -> ""
313 | Some v -> value_comparer.string_of v);
317 (* compare two maps (association lists). We use literal equality on the keys,
318 match those up first and then use the value_comparer on the values
320 let alist_comparer value_comparer ktostring = {
321 comparer = (fun al1 al2 ->
322 let vtostring = value_comparer.string_of in
323 let vsize = value_comparer.size_of in
324 let k1 = keys_of al1 in
325 let k2 = keys_of al2 in
326 let both = intersect k1 k2 in
327 let k1only = difference k1 k2 in
328 let k2only = difference k2 k1 in
329 let dels =
330 joinmap (fun k ->
331 let v = List.assoc k al1 in
332 let s = vsize v in
333 (s,(s, [Log.Del ((ktostring k) ^ "->" ^ (vtostring v))])))
334 k1only in
335 let adds =
336 joinmap (fun k -> let v = List.assoc k al2 in
337 let s = vsize v in
338 (s,(s, [Log.Add ((ktostring k) ^ "->" ^ (vtostring v))])))
339 k2only in
340 let diffs = joinmap (fun k -> let v1 = List.assoc k al1 in
341 let v2 = List.assoc k al2 in
342 Log.debug (Tty.Normal Tty.Blue) @@
343 Printf.sprintf "comparing key %s" (ktostring k);
344 let (d,(s,e)) = value_comparer.comparer v1 v2 in
345 let expanded_edits =
346 if d=0 then e
347 else [Log.Def ("for " ^ (ktostring k) ^ ":")] @ e in
348 (d,(s,expanded_edits)))
349 both in
350 joindiffs [dels; adds; diffs]);
351 size_of = (fun l -> sumsize (fun (_k,v) -> value_comparer.size_of v) l);
352 string_of = (fun l -> "[" ^ (concatstrs (List.map (fun (k,v) -> (ktostring k)
353 ^ "->" ^ (value_comparer.string_of v) ^ ";") l)) ^ "]");
356 let flag_comparer name = {
357 comparer = (fun b1 b2 -> match b1,b2 with
358 | false, false
359 | true, true -> (0, (1,[]))
360 | false, true -> (1,(1, [Log.Add name]))
361 | true, false -> (1,(1, [Log.Del name]))
363 size_of = (fun _b -> 1);
364 string_of = (fun b -> if b then name else "");
367 let function_is_async_comparer = wrap Hhas_function.is_async
368 (fun _f s -> s) (flag_comparer "isAsync")
369 let function_is_generator_comparer = wrap Hhas_function.is_generator
370 (fun _f s -> s) (flag_comparer "isGenerator")
371 let function_is_pair_generator_comparer = wrap Hhas_function.is_pair_generator
372 (fun _f s -> s) (flag_comparer "isPairGenerator")
373 let function_is_top_comparer = wrap Hhas_function.is_top
374 (fun _f s -> s) (flag_comparer "isTop")
375 let function_no_injection_comparer = wrap Hhas_function.no_injection
376 (fun _f s -> s) (flag_comparer "noInjection")
377 let function_inout_wrapper_comparer = wrap Hhas_function.inout_wrapper
378 (fun _f s -> s) (flag_comparer "inoutWrapper")
379 let function_is_return_by_ref_comparer = wrap Hhas_function.is_return_by_ref
380 (fun _f s -> s) (flag_comparer "isReturnByRef")
381 let function_is_interceptable_comparer = wrap Hhas_function.is_interceptable
382 (fun _f s -> s) (flag_comparer "isInterceptable")
384 let method_is_abstract_comparer = wrap Hhas_method.is_abstract
385 (fun _f s -> s) (flag_comparer "isAbstract")
386 let method_is_protected_comparer = wrap Hhas_method.is_protected
387 (fun _f s -> s) (flag_comparer "isProtected")
388 let method_is_public_comparer = wrap Hhas_method.is_public
389 (fun _f s -> s) (flag_comparer "isPublic")
390 let method_is_private_comparer = wrap Hhas_method.is_private
391 (fun _f s -> s) (flag_comparer "isPrivate")
392 let method_is_static_comparer = wrap Hhas_method.is_static
393 (fun _f s -> s) (flag_comparer "isStatic")
394 let method_is_final_comparer = wrap Hhas_method.is_final
395 (fun _f s -> s) (flag_comparer "isFinal")
396 let method_is_async_comparer = wrap Hhas_method.is_async
397 (fun _f s -> s) (flag_comparer "isAsync")
398 let method_is_generator_comparer = wrap Hhas_method.is_generator
399 (fun _f s -> s) (flag_comparer "isGenerator")
400 let method_is_pair_generator_comparer = wrap Hhas_method.is_pair_generator
401 (fun _f s -> s) (flag_comparer "isPairGenerator")
402 let method_is_closure_body_comparer = wrap Hhas_method.is_closure_body
403 (fun _f s -> s) (flag_comparer "isClosureBody")
404 let method_no_injection_comparer = wrap Hhas_method.no_injection
405 (fun _f s -> s) (flag_comparer "noInjection")
406 let method_is_return_by_ref_comparer = wrap Hhas_method.is_return_by_ref
407 (fun _f s -> s) (flag_comparer "isReturnByRef")
408 let method_is_interceptable_comparer = wrap Hhas_method.is_interceptable
409 (fun _f s -> s) (flag_comparer "isInterceptable")
411 (* Could have used fold earlier here *)
412 let method_flags_comparer =
413 List.fold_left (join (fun s1 s2 -> s1 ^ s2)) method_is_protected_comparer
414 [method_is_public_comparer; method_is_private_comparer;
415 method_is_static_comparer; method_is_final_comparer; method_is_async_comparer;
416 method_is_generator_comparer; method_is_pair_generator_comparer;
417 method_is_closure_body_comparer; method_is_abstract_comparer;
418 method_no_injection_comparer; method_is_return_by_ref_comparer;
419 method_is_interceptable_comparer]
421 let function_flags_comparer =
422 List.fold_left (join (fun s1 s2 -> s1 ^ s2)) function_is_async_comparer
423 [function_is_generator_comparer; function_is_pair_generator_comparer;
424 function_is_top_comparer; function_no_injection_comparer;
425 function_inout_wrapper_comparer; function_is_return_by_ref_comparer;
426 function_is_interceptable_comparer]
428 (* map of function names to functions
429 now only selects the top-level ones - others are compared dynamically
431 let top_functions_alist_of_program p =
432 List.map (fun f -> (Hhbc_id.Function.to_raw_string (Hhas_function.name f), f))
433 (List.filter Hhas_function.is_top @@ Hhas_program.functions p)
435 let methods_alist_of_class c =
436 List.map (fun m -> (Hhbc_id.Method.to_raw_string (Hhas_method.name m), m))
437 (Hhas_class.methods c)
439 let name_comparer = string_comparer
440 let param_name_comparer = wrap Hhas_param.name
441 (fun _p s -> s) name_comparer
442 let param_is_reference_comparer = wrap Hhas_param.is_reference
443 (fun p _s -> if Hhas_param.is_reference p
444 then "&" else "")
445 bool_comparer
446 let param_is_variadic_comparer = wrap Hhas_param.is_variadic
447 (fun p _s -> if Hhas_param.is_variadic p
448 then "..." else "")
449 bool_comparer
450 let tc_flags_comparer = wrap Hhas_type_constraint.flags (fun _c s -> s)
451 (primitive_set_comparer
452 Hhas_type_constraint.string_of_flag)
453 let tc_name_comparer = wrap Hhas_type_constraint.name (fun _c s -> s)
454 (option_comparer string_comparer)
455 let type_constraint_comparer = join (fun s1 s2 -> s1 ^ " " ^ s2)
456 tc_name_comparer tc_flags_comparer
457 let type_info_user_type_comparer = wrap Hhas_type_info.user_type
458 (fun _ti s -> s)
459 (option_comparer string_comparer)
460 let type_info_type_constraint_comparer = wrap Hhas_type_info.type_constraint
461 (fun _ti s -> s)
462 type_constraint_comparer
463 let type_info_comparer = join (fun s1 s2 -> "<" ^ s1 ^ " " ^ s2 ^ ">")
464 type_info_user_type_comparer
465 type_info_type_constraint_comparer
467 let attribute_comparer =
468 join (fun s1 s2 -> s1 ^ "(" ^ s2 ^ ")")
469 (wrap Hhas_attribute.name (fun _a s -> s) string_comparer)
470 (wrap Hhas_attribute.arguments (fun _l s -> s)
471 (list_comparer typed_value_comparer " "))
473 let param_attributes_comparer =
474 wrap Hhas_param.user_attributes (fun _ s -> s)
475 (list_comparer attribute_comparer " ")
477 let param_type_info_comparer = wrap Hhas_param.type_info
478 (fun _p s -> s)
479 (option_comparer type_info_comparer)
481 let param_user_attributes_is_variadic_comparer =
482 join (^) param_attributes_comparer param_is_variadic_comparer
484 let param_variadic_type_info_comparer =
485 join (fun s1 s2 -> s1 ^ s2)
486 param_user_attributes_is_variadic_comparer
487 param_type_info_comparer
488 let param_name_reference_comparer =
489 join (fun s1 s2 -> s1 ^ s2)
490 param_is_reference_comparer
491 param_name_comparer
492 let param_ti_name_reference_comparer =
493 join (fun s1 s2 -> s1 ^ s2)
494 param_variadic_type_info_comparer
495 param_name_reference_comparer
496 let param_default_value_expression_comparer =
497 wrap (function (_, (_, Ast.String (_, s))) -> s | _ -> "")
498 (fun _e s -> s) default_value_text_comparer
499 let param_default_value_comparer = wrap Hhas_param.default_value
500 (fun _p s -> s)
501 (option_comparer param_default_value_expression_comparer)
502 let param_ti_name_reference_default_value_comparer =
503 join (fun s1 s2 -> s1 ^ s2)
504 param_ti_name_reference_comparer
505 param_default_value_comparer
507 (* fix this *)
508 let params_comparer =
509 list_comparer param_ti_name_reference_default_value_comparer ", "
511 let function_params_comparer =
512 wrap Hhas_function.params
513 (fun f s ->
514 Hhbc_id.Function.to_raw_string (Hhas_function.name f)
515 ^ "(" ^ s ^ ")") params_comparer
517 let method_params_comparer =
518 wrap Hhas_method.params
519 (fun m s -> Hhbc_id.Method.to_raw_string (Hhas_method.name m)
520 ^ "(" ^ s ^ ")") params_comparer
522 let function_params_flags_comparer =
523 join (fun s1 s2 -> s1 ^ " " ^ s2)
524 function_params_comparer
525 function_flags_comparer
527 let method_params_flags_comparer =
528 join (fun s1 s2 -> s1 ^ " " ^ s2)
529 method_params_comparer
530 method_flags_comparer
532 let function_return_type_comparer =
533 wrap Hhas_function.return_type (fun _f s -> s)
534 (option_comparer type_info_comparer)
536 let method_return_type_comparer =
537 wrap Hhas_method.return_type (fun _f s -> s)
538 (option_comparer type_info_comparer)
540 let make_attributes_comparer f =
541 wrap f (fun _ s -> s) (list_comparer attribute_comparer " ")
543 let function_attributes_comparer =
544 make_attributes_comparer Hhas_function.attributes
546 let method_attributes_comparer =
547 make_attributes_comparer Hhas_method.attributes
549 let class_attributes_comparer =
550 make_attributes_comparer Hhas_class.attributes
552 let typedef_attributes_comparer =
553 make_attributes_comparer Hhas_typedef.attributes
555 let type_constants_alist c = List.map
556 (fun f -> (Hhas_type_constant.name f, Hhas_type_constant.initializer_t f))
557 (Hhas_class.type_constants c)
559 let class_constants_alist c = List.map
560 (fun f -> (Hhas_constant.name f, Hhas_constant.value f))
561 (Hhas_class.constants c)
563 let class_constants_comparer =
564 wrap class_constants_alist (fun _ s -> s)
565 (alist_comparer (option_comparer typed_value_comparer) (fun cname -> cname))
567 let class_type_constants_comparer =
568 wrap type_constants_alist (fun _ s -> s)
569 (alist_comparer (option_comparer typed_value_comparer) (fun cname -> cname))
571 let unmangled_name_comparer = {
572 comparer = (fun s1 s2 ->
573 match Hhbc_string_utils.Closures.unmangle_closure s1,
574 Hhbc_string_utils.Closures.unmangle_closure s2 with
575 | None, None -> string_comparer.comparer s1 s2
576 | Some s1', Some s2' -> string_comparer.comparer s1' s2'
577 | _, _ -> (1,(1,substedit s1 s2)));
578 size_of = (fun _n -> 1);
579 string_of = fun s -> s;
582 let class_comparer =
583 wrap Hhbc_id.Class.to_raw_string (fun _ s -> s) unmangled_name_comparer
585 let class_base_comparer =
586 wrap Hhas_class.base (fun _ s -> "extends " ^ s)
587 (option_comparer class_comparer)
589 let class_implements_comparer =
590 wrap Hhas_class.implements (fun _ s -> "implements (" ^ s ^ ")")
591 (list_comparer class_comparer " ")
592 let class_name_comparer =
593 wrap Hhas_class.name (fun _ s -> s) class_comparer
595 let class_name_base_implements_comparer =
596 join (fun s1 s2 -> s1 ^ s2)
597 class_name_comparer
598 (join (fun s1 s2 -> s1^ s2)
599 class_base_comparer
600 class_implements_comparer)
602 let class_is_final_comparer =
603 wrap Hhas_class.is_final (fun _f s -> s) (flag_comparer "final")
604 let class_is_abstract_comparer =
605 wrap Hhas_class.is_abstract (fun _f s -> s) (flag_comparer "abstract")
606 let class_is_interface_comparer =
607 wrap Hhas_class.is_interface (fun _f s -> s) (flag_comparer "interface")
608 let class_is_top_comparer =
609 wrap Hhas_class.is_top (fun _f s -> s) (flag_comparer "top")
610 let class_is_trait_comparer =
611 wrap Hhas_class.is_trait (fun _f s -> s) (flag_comparer "trait")
612 let class_is_xhp_comparer =
613 wrap Hhas_class.is_xhp (fun _f s -> s) (flag_comparer "xhp")
615 (* TODO: sensible formatting, split uses and enumtype off from flags *)
616 let class_flags_comparer =
617 List.fold_left (fun c1 c2 -> join (fun s1 s2 -> s1 ^ " " ^ s2) c1 c2)
618 class_is_final_comparer
619 [class_is_abstract_comparer; class_is_interface_comparer;
620 class_is_top_comparer; class_is_trait_comparer; class_is_xhp_comparer]
622 let class_attributes_flags_comparer =
623 join (fun s1 s2 -> "[" ^ s1 ^ " " ^ s2 ^ "]")
624 class_attributes_comparer
625 class_flags_comparer
627 let class_header_comparer =
628 join (fun s1 s2 -> s1 ^ s2)
629 class_attributes_flags_comparer
630 class_name_base_implements_comparer
632 let class_use_alias_string (a, b, c, d) =
633 let a' = match a with
634 | None -> ""
635 | Some a -> a ^ "::" in
636 let c' = match c with
637 | None -> ""
638 | Some c -> " as " ^ c in
639 let d' =
640 String.concat " " @@
641 List.map Ast.string_of_kind (List.sort compare d) in
642 a' ^ b ^ c' ^ d'
644 let class_use_precedence_string (a, b, c) =
645 let c' = String.concat " " (List.sort compare c) in
646 a ^ "::" ^ b ^ " insteadof " ^ c'
648 let class_use_alias_comparer =
649 wrap class_use_alias_string (fun _ s -> s) string_comparer
651 let class_use_precedence_comparer =
652 wrap class_use_precedence_string (fun _ s -> s) string_comparer
654 let class_use_aliases_comparer =
655 wrap Hhas_class.class_use_aliases (fun _f s -> s)
656 (list_comparer class_use_alias_comparer " ")
658 let class_use_precedences_comparer =
659 wrap Hhas_class.class_use_precedences (fun _f s -> s)
660 (list_comparer class_use_precedence_comparer " ")
662 let property_is_private_comparer =
663 wrap Hhas_property.is_private (fun _f s -> s) (flag_comparer "private")
664 let property_is_protected_comparer =
665 wrap Hhas_property.is_protected (fun _f s -> s) (flag_comparer "protected")
666 let property_is_public_comparer =
667 wrap Hhas_property.is_public (fun _f s -> s) (flag_comparer "public")
668 let property_is_static_comparer =
669 wrap Hhas_property.is_static (fun _f s -> s) (flag_comparer "static")
670 let property_is_deep_init_comparer =
671 wrap Hhas_property.is_deep_init (fun _f s -> s) (flag_comparer "deep_init")
672 let property_no_serialize_comparer =
673 wrap Hhas_property.no_serialize (fun _f s -> s) (flag_comparer "no_serialize")
674 let prop_comparer =
675 wrap Hhbc_id.Prop.to_raw_string (fun _ s -> s) string_comparer
677 let property_name_comparer =
678 wrap Hhas_property.name (fun _ s -> s) prop_comparer
679 let property_initial_value_comparer =
680 wrap Hhas_property.initial_value (fun _ s -> s)
681 (option_comparer typed_value_comparer)
682 let property_type_info_comparer =
683 wrap Hhas_property.type_info (fun _ s -> s) (type_info_comparer)
685 (* TODO: format these much more sensibly *)
686 let property_comparer =
687 List.fold_left (fun c1 c2 -> join (fun s1 s2 -> s1 ^ s2) c1 c2)
688 property_is_private_comparer
689 [property_is_protected_comparer;
690 property_is_public_comparer;
691 property_is_static_comparer;
692 property_is_deep_init_comparer;
693 property_name_comparer;
694 property_initial_value_comparer;
695 property_no_serialize_comparer;
696 property_type_info_comparer]
698 (* apply a permutation to the trailing elements of a list
699 used to reorder the properties of one closure class to
700 match them up with those of a corresponding one *)
701 let permute_property_list perm ps =
702 let offset = List.length ps - List.length perm in
703 let sorted_perm = List.sort (fun (a,_) (b,_) -> compare a b) perm in
704 let permuted_tail = List.map (fun (_,i) -> List.nth ps (offset+i)) sorted_perm in
705 Hh_core.List.take ps offset @ permuted_tail
707 let property_list_comparer perm =
708 let lc = list_comparer property_comparer "\n" in
710 comparer = (fun l1 l2 ->
711 let permuted_l2 = permute_property_list perm l2 in
712 (if perm = [] then ()
713 else let l1names = concatstrs
714 (List.map (fun p -> Hhbc_id.Prop.to_raw_string (Hhas_property.name p)) l1) in
715 let l2names = concatstrs
716 (List.map (fun p -> Hhbc_id.Prop.to_raw_string (Hhas_property.name p)) permuted_l2) in
717 Log.debug (Tty.Normal Tty.Blue) @@ Printf.sprintf "properties %s and %s" l1names l2names);
718 lc.comparer l1 permuted_l2);
719 size_of = lc.size_of;
720 string_of = lc.string_of;
723 let class_properties_comparer perm =
724 wrap Hhas_class.properties (fun _ s -> s) (property_list_comparer perm)
726 let function_attributes_return_type_comparer =
727 join (fun s1 s2 -> s1 ^ s2)
728 function_attributes_comparer
729 function_return_type_comparer
731 let method_attributes_return_type_comparer =
732 join (fun s1 s2 -> s1 ^ s2)
733 method_attributes_comparer
734 method_return_type_comparer
736 let function_header_comparer =
737 join (fun s1 s2 -> s1 ^ s2) function_attributes_return_type_comparer
738 function_params_flags_comparer
740 let method_header_comparer =
741 join (fun s1 s2 -> s1 ^ s2) method_attributes_return_type_comparer
742 method_params_flags_comparer
744 (* checking declvars as a list, not a set, as order does matter
745 we also take a permutation, though this should always be the
746 identity except in the case of a closure class's single
747 method
749 let permute_decl_list perm ds =
750 if perm = [] then ds
751 else let sorted_perm = List.sort (fun (a,_) (b,_) -> compare a b) perm in
752 let sorted_section = List.map (fun (_,i) -> List.nth ds (1+i)) sorted_perm in
753 (List.hd ds) :: (sorted_section @ Hh_core.List.drop ds (List.length perm + 1))
755 let decl_list_comparer perm =
756 let lc = list_comparer string_comparer "," in
758 comparer = (fun l1 l2 ->
759 let permuted_l2 = permute_decl_list perm l2 in
760 (if perm = [] then ()
761 else let l1names = concatstrs l1 in
762 let l2names = concatstrs permuted_l2 in
763 Log.debug (Tty.Normal Tty.Blue) @@ Printf.sprintf "declvars %s and %s" l1names l2names);
764 lc.comparer l1 permuted_l2);
765 size_of = lc.size_of;
766 string_of = lc.string_of;
769 let body_decl_vars_comparer perm =
770 wrap Hhas_body.decl_vars (fun _f s -> s) (decl_list_comparer perm)
772 let body_num_iters_comparer =
773 wrap Hhas_body.num_iters (fun _ s -> "numiters = " ^ s) int_comparer
775 let body_num_cls_ref_slots_comparer =
776 wrap Hhas_body.num_cls_ref_slots (fun _ s -> "numclsrefslots = " ^ s)
777 int_comparer
779 let body_iters_cls_ref_slots_comparer =
780 join (fun s1 s2 -> s1 ^ "\n" ^ s2)
781 body_num_iters_comparer
782 body_num_cls_ref_slots_comparer
784 let body_iters_cls_ref_slots_decl_vars_comparer perm =
785 join (fun s1 s2 -> s1 ^ "\n" ^ s2)
786 body_iters_cls_ref_slots_comparer
787 (body_decl_vars_comparer perm)
789 let instruct_comparer = primitive_comparer Utils.string_of_instruction
791 let instruct_list_comparer = list_comparer instruct_comparer "\n"
793 let option_get o = match o with | Some v -> v | None -> failwith "option"
794 let option_is_some o = match o with Some _ -> true | None -> false
796 (* compare two bodies' instructions, with extra entry points added for
797 default parameter values, dropping back to syntactic diff on failure
799 let body_instrs_comparer = {
800 comparer = (fun b b' ->
801 let todo = match Hh_core.List.zip (Hhas_body.params b) (Hhas_body.params b') with
802 | None -> [] (* different lengths so just look at initial entry point *)
803 | Some param_pairs ->
804 let params_with_defaults = List.filter
805 (fun (p,p') -> option_is_some (Hhas_param.default_value p) &&
806 option_is_some (Hhas_param.default_value p')) param_pairs in
807 List.map (fun (p,p') ->
808 (fst (option_get (Hhas_param.default_value p)),
809 fst (option_get (Hhas_param.default_value p')))) params_with_defaults
811 let inss = Instruction_sequence.instr_seq_to_list (Hhas_body.instrs b) in
812 let inss' = Instruction_sequence.instr_seq_to_list (Hhas_body.instrs b') in
813 match Rhl.equiv inss inss' todo with
814 | None -> (Log.debug (Tty.Normal Tty.White) "Semdiff succeeded";
815 (0, (List.length inss, [])) )
816 | Some (pc,pc',asn,assumed,todo) ->
817 (Log.debug (Tty.Normal Tty.White) "Semdiff failed";
818 Log.debug (Tty.Normal Tty.White) @@ Printf.sprintf
819 "pc=%s, pc'=%s, i=%s i'=%s asn=%s\n"
820 (Rhl.string_of_pc pc) (Rhl.string_of_pc pc')
821 (Rhl.string_of_nth_instruction inss pc)
822 (Rhl.string_of_nth_instruction inss' pc')
823 (Rhl.asntostring asn);
824 let assumed_str = Rhl.labasnsmaptostring assumed in
825 let todo_str = Rhl.labasnlisttostring todo in
826 if assumed_str <> "" && not !Log.hide_assm
827 then Log.debug (Tty.Normal Tty.White) @@
828 Printf.sprintf "Assumed=\n%s\n" assumed_str;
829 if todo_str <> "" && not !Log.hide_assm
830 then Log.debug (Tty.Normal Tty.White) @@
831 Printf.sprintf"Todo=%s" todo_str);
832 let is_isrcloc = function Hhbc_ast.ISrcLoc _ -> true | _ -> false in
833 let remove_isrclocs = List.filter (fun i -> not @@ is_isrcloc i) in
834 let inss = remove_isrclocs inss in
835 let inss' = remove_isrclocs inss' in
836 instruct_list_comparer.comparer inss inss');
837 size_of = (fun b -> instruct_list_comparer.size_of
838 (Instruction_sequence.instr_seq_to_list (Hhas_body.instrs b)));
839 string_of = (fun b -> instruct_list_comparer.string_of
840 (Instruction_sequence.instr_seq_to_list (Hhas_body.instrs b)));
843 let body_static_inits_comparer =
844 wrap Hhas_body.static_inits (fun _f s -> s)
845 (primitive_set_comparer (fun s -> s))
847 let body_is_memoize_wrapper_comparer =
848 wrap Hhas_body.is_memoize_wrapper (fun _f s -> s) (flag_comparer "memoize")
850 let body_iters_cls_ref_slots_decl_vars_instrs_comparer perm =
851 join (fun s1 s2 -> s1 ^ "\n" ^ s2)
852 (body_iters_cls_ref_slots_decl_vars_comparer perm)
853 body_instrs_comparer
855 let body_comparer perm =
856 List.fold_left (join (fun s1 s2 -> s1 ^ s2)) body_is_memoize_wrapper_comparer
857 [body_static_inits_comparer;
858 body_iters_cls_ref_slots_decl_vars_instrs_comparer perm]
860 let function_body_comparer =
861 wrap Hhas_function.body (fun _ s -> s) (body_comparer [])
863 let method_body_comparer perm =
864 wrap Hhas_method.body (fun _ s -> s) (body_comparer perm)
866 let function_header_body_comparer =
867 join (fun s1 s2 -> s1 ^ "{\n" ^ s2 ^ "}\n") function_header_comparer
868 function_body_comparer
870 let method_header_body_comparer perm =
871 join (fun s1 s2 -> s1 ^ "{\n" ^ s2 ^ "}\n") method_header_comparer
872 (method_body_comparer perm)
874 let program_main_comparer =
875 wrap Hhas_program.main (fun _p s -> s) (body_comparer [])
876 |> debug_log_with ".main"
879 let functions_alist_comparer =
880 alist_comparer function_header_body_comparer (fun fname -> fname)
882 let methods_alist_comparer perm =
883 alist_comparer (method_header_body_comparer perm) (fun mname -> mname)
886 let class_methods_comparer perm = wrap methods_alist_of_class
887 (fun _c s -> s) (methods_alist_comparer perm)
889 let class_properties_methods_comparer perm =
890 join (fun s1 s2 -> s1 ^ s2)
891 (class_properties_comparer perm)
892 (class_methods_comparer perm)
894 let class_properties_methods_use_aliases_comparer perm =
895 join (fun s1 s2 -> s1 ^ s2)
896 (class_properties_methods_comparer perm)
897 class_use_aliases_comparer
899 let class_properties_methods_use_aliases_precedences_comparer perm =
900 join (fun s1 s2 -> s1 ^ s2)
901 (class_properties_methods_use_aliases_comparer perm)
902 class_use_precedences_comparer
904 let class_constants_type_constants_comparer =
905 join (fun s1 s2 -> s1 ^ s2)
906 class_constants_comparer
907 class_type_constants_comparer
909 let class_header_properties_methods_comparer perm =
910 join (fun s1 s2 -> s1 ^ "{\n" ^ s2 ^ "}")
911 class_header_comparer
912 (class_properties_methods_use_aliases_precedences_comparer perm)
914 (* TODO: add all the other bits to classes *)
915 let class_comparer perm =
916 join (fun s1 s2 -> s1 ^ s2)
917 class_constants_type_constants_comparer
918 (class_header_properties_methods_comparer perm)
920 let typedef_name_comparer =
921 wrap (fun def -> Hhbc_id.Class.to_raw_string @@ Hhas_typedef.name def)
922 (fun _ s -> s) string_comparer
924 let typedef_name_attributes_comparer =
925 join (fun s1 s2 -> s1 ^ s2)
926 typedef_attributes_comparer
927 typedef_name_comparer
929 let typedef_type_info_comparer =
930 wrap Hhas_typedef.type_info (fun _ s -> s) type_info_comparer
932 let typedef_type_structure_comparer =
933 wrap Hhas_typedef.type_structure
934 (fun _ s -> s) (option_comparer typed_value_comparer)
936 let typedef_type_info_and_structure_comparer =
937 join (fun s1 s2 -> "< " ^ s1 ^ " > " ^ s2)
938 typedef_type_info_comparer
939 typedef_type_structure_comparer
941 let typedef_comparer =
942 join (fun s1 s2 -> s1 ^ " = " ^ s2)
943 typedef_name_attributes_comparer
944 typedef_type_info_and_structure_comparer
946 let program_top_functions_comparer = wrap top_functions_alist_of_program
947 (fun _p s -> s) functions_alist_comparer
949 let program_main_top_functions_comparer =
950 join (fun s1 s2 -> s1 ^ s2) program_main_comparer program_top_functions_comparer
952 (* Refactoring so that all comparison of classes is triggered off the dynamic
953 use of DefCls etc. in main and top-level functions (and then recursively by
954 methods therein), rather than by a static association list.
955 This should even do the "right" thing in the case that there
956 are multiple classes with the same name that are dynamically registered
957 Now doing the same kind of thing for nontop-level functions *)
958 let classes_todosplitter s =
959 if Rhl.IntIntPermSet.is_empty s then None
960 else let iip = Rhl.IntIntPermSet.choose s in
961 Some (iip, Rhl.IntIntPermSet.remove iip s)
963 let intintset_todosplitter s =
964 if Rhl.IntIntSet.is_empty s then None
965 else let ii = Rhl.IntIntSet.choose s in
966 Some (ii, Rhl.IntIntSet.remove ii s)
968 let adata_todosplitter s =
969 if Rhl.StringStringSet.is_empty s then None
970 else let ss = Rhl.StringStringSet.choose s in
971 Some (ss, Rhl.StringStringSet.remove ss s)
973 let compare_classes_functions_of_programs p p' =
974 Rhl.classes_to_check := Rhl.IntIntPermSet.empty;
975 Rhl.classes_checked := Rhl.IntIntSet.empty;
976 Rhl.functions_to_check := Rhl.IntIntSet.empty;
977 Rhl.functions_checked := Rhl.IntIntSet.empty;
978 Rhl.adata_to_check := Rhl.StringStringSet.empty;
979 Rhl.adata_checked := Rhl.StringStringSet.empty;
980 (* clear refs here again just to be on the safe side
981 start by comparing top-level stuff
983 let (dist, (size,edits)) = program_main_top_functions_comparer.comparer p p' in
984 let rec loop d s e =
985 let td = !Rhl.classes_to_check in
986 match classes_todosplitter td with
987 | None -> (match intintset_todosplitter !Rhl.functions_to_check with
988 | None -> (match adata_todosplitter !Rhl.adata_to_check with
989 | None -> (match intintset_todosplitter !Rhl.typedefs_to_check with
990 | None -> (d,(s,e))
991 | Some ((tid, tid'), newdefstodo) ->
992 Rhl.typedefs_to_check := newdefstodo;
993 if Rhl.IntIntSet.mem (tid, tid') !Rhl.typedefs_checked
994 then loop d s e (* already checked *)
995 else
996 let typedef = List.nth (Hhas_program.typedefs p) tid in
997 let typedef' = List.nth (Hhas_program.typedefs p') tid' in
998 Rhl.typedefs_checked := Rhl.IntIntSet.add (tid, tid') !Rhl.typedefs_checked;
999 let (df, (sf, ef)) = typedef_comparer.comparer typedef typedef' in
1000 loop (d+df) (s+sf) (e @ ef)
1002 | Some ((id, id'), newdatatodo) ->
1003 Rhl.adata_to_check := newdatatodo;
1004 if Rhl.StringStringSet.mem (id, id') !Rhl.adata_checked
1005 then loop d s e (* already checked *)
1006 else
1007 let adata = List.find (fun x -> (Hhas_adata.id x) = id) (Hhas_program.adata p) in
1008 let adata' = List.find (fun x -> (Hhas_adata.id x) = id') (Hhas_program.adata p') in
1009 Rhl.adata_checked := Rhl.StringStringSet.add (id, id') !Rhl.adata_checked;
1010 let (df, (sf, ef)) = typed_value_comparer.comparer
1011 (Hhas_adata.value adata) (Hhas_adata.value adata') in
1012 let ef =
1013 match ef with
1014 | [] -> []
1015 | _ -> Log.Def ("for .adata " ^ id ^ " with " ^ id') :: ef in
1016 loop (d+df) (s+sf) (e @ ef)
1018 | Some ((fid,fid'),newftodo) ->
1019 Rhl.functions_to_check := newftodo;
1020 if Rhl.IntIntSet.mem (fid,fid') !Rhl.functions_checked
1021 then loop d s e (* already done *)
1022 else
1023 (* Note nasty subtraction of one here. Seems like we count .main as the
1024 zeroth function, which is OK provided that main is always there and
1025 always first. Since hhvm complains if main isn't first, I think this
1026 is OK *)
1027 let actual_function = List.nth (Hhas_program.functions p) (fid - 1) in
1028 let actual_function' = List.nth (Hhas_program.functions p') (fid' -1) in
1029 Rhl.functions_checked := Rhl.IntIntSet.add (fid,fid') !Rhl.functions_checked;
1030 Log.debug (Tty.Normal Tty.Blue) @@
1031 Printf.sprintf "dynamic function comparison %d=%s and %d=%s"
1032 fid (Hhbc_id.Function.to_raw_string @@ Hhas_function.name actual_function)
1033 fid' (Hhbc_id.Function.to_raw_string @@ Hhas_function.name actual_function') ;
1034 if Hhas_function.is_top actual_function || Hhas_function.is_top actual_function'
1035 then failwith "dynamic comparison of top-level function"
1036 else ();
1037 let (df, (sf,ef)) = function_header_body_comparer.comparer
1038 actual_function actual_function' in
1039 loop (d+df) (s+sf) (e @ ef)
1041 | Some ((ac,ac',perm), newtodo) ->
1042 (Rhl.classes_to_check := newtodo;
1043 if Rhl.IntIntSet.mem (ac,ac') (!Rhl.classes_checked)
1044 then loop d s e (* already done this pair *)
1045 else
1046 let actual_class = List.nth (Hhas_program.classes p) ac in
1047 let class_name = Hhas_class.name actual_class |> Hhbc_id.Class.to_raw_string in
1048 let actual_class' = List.nth (Hhas_program.classes p') ac' in
1049 Rhl.classes_checked := Rhl.IntIntSet.add (ac,ac') (!Rhl.classes_checked);
1050 Log.debug (Tty.Normal Tty.Blue) @@
1051 Printf.sprintf "comparing .class %s" class_name;
1052 let (dc, (sc,ec)) = (class_comparer perm).comparer actual_class actual_class' in
1053 (if perm = [] then ()
1054 else Log.debug (Tty.Normal Tty.Blue) @@
1055 Printf.sprintf "did perm comparison on classes %d and %d, distance was %d" ac ac' dc);
1056 loop (d+dc) (s+sc)
1057 (e @ debug_edit_log_with ("in .class " ^ class_name) ec))
1058 in loop dist size edits
1060 (* TODO: sizes and printing are not quite right here,
1061 as they don't take nontop functions into account *)
1062 let program_main_functions_classes_comparer = {
1063 comparer = compare_classes_functions_of_programs;
1064 size_of = (fun p -> program_main_top_functions_comparer.size_of p
1065 + sumsize (class_comparer []).size_of (Hhas_program.classes p));
1066 string_of = (fun p -> program_main_top_functions_comparer.string_of p ^
1067 String.concat "\n"
1068 (List.map (class_comparer []).string_of (Hhas_program.classes p)));
1071 (* top level comparison for whole programs *)
1072 let program_comparer = program_main_functions_classes_comparer