1. Trang chủ
  2. » Luận Văn - Báo Cáo

Phân tích tĩnh chương trình bằng phương pháp giải thích trừu tượng

65 9 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 65
Dung lượng 2,57 MB

Nội dung

ĐẠI HỌC THÁI NGUYÊN TRƯỜNG ĐẠI HỌC CÔNG NGHỆ THÔNG TIN VÀ TRUYỀN THƠNG NGUYỄN THANH BẰNG PHÂN TÍCH TĨNH CHƯƠNG TRÌNH BẰNG PHƯƠNG PHÁP GIẢI THÍCH TRỪU TƯỢNG LUẬN VĂN THẠC SĨ KHOA HỌC MÁY TÍNH Thái Nguyên, năm 2013 ĐẠI HỌC THÁI NGUYÊN TRƯỜNG ĐẠI HỌC CÔNG NGHỆ THƠNG TIN VÀ TRUYỀN THƠNG NGUYỄN THANH BẰNG PHÂN TÍCH TĨNH CHƯƠNG TRÌNH BẰNG PHƯƠNG PHÁP GIẢI THÍCH TRỪU TƯỢNG Chuyên ngành : KHOA HỌC MÁY TÍNH Mã số : 60.48.01 NGƯỜI HƯỚNG DẪN KHOA HỌC: TS Nguyễn Trường Thắng Thái Nguyên, năm 2013 LỜI CAM ĐOAN Tôi xin cam đoan tất nội dung đƣợc trình bày nội dung luận văn nghiên cứu viết dƣới hƣớng dẫn TS Nguyễn Trƣờng Thắng – Viện Công nghệ thông tin – Viện Khoa học công nghệ Việt Nam hƣớng dẫn Không có chép ngồi việc tham khảo từ tài liệu nhƣ trình bày phần tài liệu tham khảo Nếu có hình thức gian lận tơi xin hồn tồn chịu trách nhiệm Thái Nguyên, tháng 01 năm 2013 Học viên cao học khóa Chun ngành: Khoa học máy tính Trƣờng đại học Công nghệ thông tin truyền thông Đại học Thái Nguyên MỤC LỤC LỜI CAM ĐOAN MỤC LỤC DANH MỤC CÁC KÝ HIỆU, CÁC CHỮ VIẾT TẮT DANH MỤC CÁC THUẬT NGỮ DANH MỤC CÁC HÌNH MỞ ĐẦU CHƢƠNG I TỔNG QUAN 10 1.1 Công nghệ phần mềm vấn đề liên quan 10 1.1.1 Khái niệm công nghệ phần mềm 10 1.1.2 Quy trình phát triển phần mềm 11 1.2 Các kỹ thuật công nghệ phần mềm nhằm nâng cao chất lƣợng phần mềm 13 1.2.1 Kiểm chứng phần mềm 14 1.2.2 Phân tích mã nguồn tĩnh 15 1.2.3 Kiểm thử phần mềm 17 1.2.4 So sánh kiểm chứng mơ hình kiểm thử phần mềm 18 1.3 Kết luận chƣơng 18 CHƢƠNG II PHƢƠNG PHÁP GIẢI THÍCH TRỪU TƢỢNG 19 2.1 Khái niệm giải thích trừu tƣợng 19 2.2 Ứng dụng giải thích trừu tƣợng 19 2.3 Một số khái niệm 20 2.3.1 Ngữ nghĩa 20 2.3.2 Tính an tồn: 20 2.3.3 Giải thích trừu tƣợng: 21 2.3.4 Tiêu chuẩn trừu tƣợng hóa: 22 2.3.5 Miền trừu tƣợng: (Abstract domains): 22 2.3.6 Vết thực thi 23 2.3.7 Thu thập ngữ nghĩa: 25 2.4 Nền tảng tốn học giải thích trừu tƣợng 26 2.4.1 Liên kết nhị phân 26 2.4.2 Tập có thứ tự phần (Poset) 26 2.4.3 Cấu trúc dàn (Lattices) 27 2.4.4 Sơ đồ Hasse 28 2.4.5 Điểm cố định (Fixpoint) 28 2.4.6 Bƣớc lặp 29 2.4.7 Kết nối Galois 29 2.5 Giải thích trừu tƣợng 29 2.5.1 Đối tƣợng trừu tƣợng (Abstract objects) 30 2.5.2 Thuộc tính trừu tƣợng 31 2.5.3 Giải thích trừu tƣợng phân tích tĩnh chƣơng trình: 32 2.5.4 Kết luận chƣơng 34 CHƢƠNG III CHƢƠNG TRÌNH THỰC NGHIỆM 35 3.1 Giới thiệu TVLA 35 3.2 Nền tảng toán học TVLA 36 3.2.1 Giá trị 3-logic 37 3.2.2 Phƣơng pháp 39 3.3 Phân tĩnh tĩnh chƣơng trình sử dụng TVLA 43 3.3.1 Bài toán 43 3.3.2 Bài toán 48 3.4 Kết luận chƣơng 53 KẾT LUẬN VÀ HƢỚNG PHÁT TRIỂN 54 TÀI LIỆU THAM KHẢO 55 PHỤ LỤC 56 Thuật toán phân tích mối quan hệ vơ hạn thơng qua lặp tiến/lùi chi tiết 56 Các định nghĩa chức trừu tƣợng action.tvp 57 Các định nghĩa chức trừu tƣợng predicates.tvp 62 Dữ liệu đầu vào TVLA phân tích chức tạo danh sách liên kết creat.tvp 64 DANH MỤC CÁC KÝ HIỆU, CÁC CHỮ VIẾT TẮT STT Từ viết tắt EASL CM PTTGTT CFG Viết đầy đủ Nghĩa Engineering Analysis and Kỹ thuật Phân tích Simulation Language ngôn ngữ mô Summary nodes Nút đại diện Phân tích tĩnh chƣơng trình giải thích trừu tƣợng Control Flow Graphc Sơ đồ luồng điều khiển DANH MỤC CÁC THUẬT NGỮ STT Ý nghĩa Thuật ngữ Tập hợp dấu vết trình Vết thực thi (Trace semantics) chuyển đổi trạng thái chƣơng trình Tập có thứ tự phần (Poset Là tập hợp phần tử có - Partial Ordered Set) thứ tự Là tập hợp phần tử có thứ tự phần có điểm chặn Cấu trúc dàn (Lattice) dƣới lớn trặn nhỏ DANH MỤC CÁC HÌNH Hình sử dụng STT Hình 1.1: Mơ hình tổng qt quy trình sản xuất phần mềm xe Hình 1.2: Tổng quan phân tích mã nguồn tĩnh Hình 1.3: MISRA-C “Subset”-Tập ngơn ngữ C Hình 1.4: Hai MISRA-C:1998 MISRA-C:2004 Hình 2.1 Sơ đồ hành vi chƣơng trình Hình 2.2 Quỹ đạo an tồn Hình 2.3 Quỹ đạo khơng an tồn Hình 2.4 Quỹ đạo hành vi trừu tƣợng chƣơng trình Hình 2.4 Phát lỗi với giải thích trừu tƣợng 10 Hình 2.5 Các bƣớc chuyển trạng thái chƣơng trình 11 Hình 2.6 Vết thực thi chƣơng trình 12 Hình 2.7 Thu thập ngữ nghĩa chƣơng trình 13 Hình 2.8 Đồ thị liên kết nhị phân 14 Hình 2.9 Thuật tốn giải thích trừu tƣợng tổng quát 15 Hình 3.1 Bảng giá trị phép giao 3-logic 16 Hình 3.2 Bảng giá trị phép hợp 3-logic 17 Hình 3.3 Bảng giá trị phép hợp 3-logic 18 Hình 3.4 Bảng trừu tƣợng hóa logic vị từ 19 Hình 3.5 Mơ trạng thái nhớ cấu trúc trỏ 20 Hình 3.6 Trạng thái chƣơng trình sử dụng 2-logic 21 Hình 3.7 Trạng thái chƣơng trình sử dụng nút đại diện (3-logic) 22 Hình 3.8 Tiến trình làm việc trình PTTGTTT 23 Hình 3.9 Thực phân tích tĩnh chƣơng trình TVLA 24 Hình 3.10 Kết phân tích chức tạo danh sách liên kết 25 Hình 3.11 Trạng thái tổng thể chƣơng trình thêm nút vào nhị phân MỞ ĐẦU Ngày nay, phần mềm xuất khắp nơi hầu hết thiết bị điện tử sử dụng phần mềm Phần mềm khơng đơn giản chƣơng trình máy tính mà bao gồm tƣ liệu lƣu trữ thơng tin vận hành giúp chƣơng trình hoạt động đƣợc Vì ứng dụng to lớn phần mềm ngành sản xuất, tài ngân hàng, y tế, bệnh viện, trƣờng học, nhà nƣớc, nên yêu cầu lớn đặt xây dựng, phát triển ứng dụng công nghệ phần mềm Trong luận văn xin giới thiệu phƣơng pháp kiểm tra đánh giá chất lƣợng phần mềm phân tích tĩnh chƣơng trình Với mục tiêu: Đƣa cách nhìn nhận việc lập xây dựng kiểm tra đắn chƣơng trình sử dụng phƣơng pháp phân tích tĩnh chƣơng trình Áp dụng cơng cụ để phân tích chƣơng trình, kiểm tra đắn chƣơng trình giải thích trừu tƣợng (Abstract Interpretation) Vì kỹ thuật phân tích chƣơng trình chƣa đƣợc nghiên cứu rộng rãi Việt Nam, nên luận văn mang tính giới thiệu ban đầu khái niệm tảng lý thuyết dựa tài liệu gốc [2], giáo trình đƣợc sử dụng đào tạo thạc sỹ Học viện Công nghệ Massachussette (MIT) – Hoa Kỳ, chƣơng Phần thử nghiệm chƣơng tập trung vào việc cài đặt thử nghiệm công cụ TVLA (3Valued Logic Analysis Engine) trƣờng đại học Khoa Học Máy Tính Tel Aviv (School of Computer Science Tel Aviv University), thành phố Tel Aviv, Isarel[6] Cơng cụ phân tích chƣơng trình sử dụng giải thích trừu tƣợng Nội dung luận văn gồm chƣơng: CHƢƠNG I TỔNG QUAN Đƣa khái niệm liên quan, cần thiết việc phân tích tĩnh chƣơng trình, giới thiệu phƣơng pháp phân tích tĩnh chƣơng trình CHƢƠNG II PHƢƠNG PHÁP GIẢI THÍCH TRỪU TƢỢNG Trình bày tảng lý thuyết, thuật tốn, ứng dụng, ƣu nhƣợc điểm phƣơng pháp giải thích trừu tƣợng.[2] CHƢƠNG III CHƢƠNG TRÌNH THỰC NGHIỆM Cài đặt phƣơng pháp phân tích tĩnh giải thích trừu tƣợng, đƣa kết thực nghiệm kết luận[6] n2 uninterpreted() n7 n2 uninterpreted() n3 // prev = cur; n3 Copy_Var_T(prev, cur) n4 // if (el->data < cur->data) n4 Less_Data_T(el, cur) n5 n4 Greater_Equal_Data_T(el, cur) n6 // cur = cur->left; n5 Get_Sel_T(cur, cur, left) n1 // else cur = cur->right; n6 Get_Sel_T(cur, cur, right) n1 // } // Don't insert duplicates // if (cur == NULL) { n7 Is_Null_Var(cur) n8 n7 Is_Not_Null_Var(cur) n7_a // Null out cur and prev, as an optimizer could n7_a Set_Null_T(cur) n7_b n7_b Set_Null_T(prev) found // if (cur == root) n8 Is_Eq_Var(cur, root) n9 n8 Is_Not_Eq_Var(cur, root) n10 // root = el; n9 Copy_Var_T(root, el) notFound // if (el->data < prev->data) n10 Less_Data_T(el, prev) n11 n10 Greater_Equal_Data_T(el, prev) n13 // prev->left = el; n11 Set_Sel_Null_T(prev, left) n12 n12 Set_Sel_T(prev, left, el) n12_a // Null out el and prev, as an optimizer could n12_a Set_Null_T(el) n12_b n12_b Set_Null_T(prev) notFound // else prev->right = el; n13 Set_Sel_Null_T(prev, right) n14 n14 Set_Sel_T(prev, right, el) n14_a // Null out el and prev, as an optimizer could 50 n14_a Set_Null_T(el) n14_b n14_b Set_Null_T(prev) notFound // } found uninterpreted() test notFound uninterpreted() test // Now test the structures test Is_Sorted_Data_T(root) exit test Is_Not_Sorted_Data_T(root) error %% error, exit, found, notFound c Kết Vì kết phân tích q lớn, nên tơi đƣa trạng thái tổng quan kết phân tích Các trạng thái chƣơng trình thời điểm đƣợc để thƣ mục examples\tree\ đĩa chƣơng trình kèm theo 51 Hình 3.11 Trạng thái tổng thể chƣơng trình thêm nút vào nhị phân 52 3.4 Kết luận chƣơng Trong chƣơng này, giới thiệu phần mềm TVLA, kiến thức tảng toán học TVLA giới thiệu toán chạy thử nghiệm chƣơng trình, đánh giá kết đạt đƣợc từ việc phân tích tốn đƣa TVLA 53 KẾT LUẬN VÀ HƢỚNG PHÁT TRIỂN Phân tích tĩnh chƣơng trình, đặc biệt phƣơng pháp giải thích trừu tƣợng lĩnh vực nghiên cứu rộng lớn, mẻ có tính mở, nhiều vấn đề cần tiếp tục nghiên cứu phát triển Trong giới hạn đồ án trình bày tổng quan phân tích tĩnh chƣơng trình nói chung phƣơng pháp giải thích trừu tƣợng phân tích tĩnh chƣơng trình nói riêng, đặc biệt tơi trình bày đƣợc vấn đề liên quan đến giải thích trừu tƣợng sở phân tích sơ đồ luồng điều khiển, áp dụng đƣợc thành công cơng cụ phân tích mã nguồn tĩnh sử dụng phƣơng pháp nêu Nhƣng vấn đề rộng lớn phải tốn nhiều thời gian, nhân lực phƣơng tiện nên phản ảnh đƣợc phần lĩnh vực Mặc dù vậy, đồ án đáp ứng đƣợc mục đích yêu cầu đƣa là: Tìm hiểu, giới thiệu tổng quan phân tích tĩnh chƣơng trình Giới thiệu phân tích tĩnh chƣơng trình phƣơng pháp giải thích trừu tƣợng vấn đề liên quan Trình bày đƣợc thuật tốn, cách mô trừu tƣợng cấu trúc 2logic cấu trúc 3-logic Vận dụng công cụ mã nguồn mở để demo thuật toán đánh giá kết đạt đƣợc Phân tích tĩnh chƣơng trình nói chung phân tích tĩnh chƣơng trình sử dụng giải thích trừu tƣợng nói riêng ứng dụng nhiều lĩnh vực khoa học máy tính lĩnh vực khác nhằm nâng cao tính hiệu phần mềm điều khiển quản lý Việc nghiên cứu, ứng dụng phân tích tĩnh cần thiết nên đƣợc trọng nhƣ môn khoa học, đặc biệt nên phát triển nhƣ lĩnh vực công nghệ phần mềm 54 TÀI LIỆU THAM KHẢO [1] David Schmidt (2005), Foundations of abstract interpretation, Kansas State University http://santos.cis.ksu.edu/schmidt/Escuela03/WSSA/talk2p.pdf [2] Course (2005), Abstract interpretation http://web.mit.edu/afs/athena.mit.edu/course/16/16.399/www/ [3] David Schmidt (2004), Applications and logics for static analysis, Kansas State University http://santos.cis.ksu.edu/schmidt/Escuela03/WSSA/talk4p.pdf [4] Arnaud Venet Guillaume Brat, Static Program Analysis using Abstract Interpretation, Kestrel Technology NASA Ames Research Center Moffett Field [5] P Cousot (Aug 5, 2008) Abstract Interpretation [6] Tel Aviv University, Tvla: a system for generating abstract interpreters http://www.cs.tau.ac.il/~tvla/ifip2004.pdf Website tham khảo: [7] Abstract Interpretation http://web.mit.edu/afs/athena.mit.edu/course/16/16.399/www/ [8] Abstract interpretation and static analysis http://santos.cis.ksu.edu/schmidt/Escuela03/home.html [9] Computer Science Departmen http://www.di.ens.fr/WebHome.html.en [10] Tel Aviv University http://www.cs.tau.ac.il/ 55 PHỤ LỤC Thuật tốn phân tích mối quan hệ vô hạn thông qua lặp tiến/lùi chi tiết (* main.ml = main-iter-fw-bw.ml *) open Program_To_Abstract_Syntax open Labels open Pretty_Print open Lpretty_Print open Abstract_To_Linear_Syntax open Linear_Syntax open Aenv open Acom open Bcom let _ = let narrowing_limit = 100 in let arg = if (Array.length Sys.argv) = then "" else Sys.argv.(1) in Random.self_init (); let p = (abstract_syntax_of_program arg) in (print_string "** Program:\n"; pretty_print p; let p’ = (linearize_com p) in print_string "** Linearized program:\n"; lpretty_print p’; init (); let rec iterate pre n = (print_string "** Precondition:\n"; print pre; let post = (acom p’ pre (after p’)) in (print_string "** Postcondition:\n"; 56 print post; let pre’ = (bcom p’ post (at p’)) in (if (Aenv.eq pre pre’) then (print_string "stable precondition after "; print_int n;print_string " iteration(s).\n") else if (n < narrowing_limit) then (print_string "unstable precondition after "; print_int n;print_string " iteration(s).\n"; iterate pre’ (n+1)) else (print_string "stable stopped "; print_int n; print_string " iterations (narrowing).\n")))) in iterate (initerr ()) 1; quit ()) Các định nghĩa chức trừu tƣợng action.tvp %action uninterpreted() { %t "uninterpreted" } %action skip() { %t "skip" } /////////////////////////////////////////////////////////////////////////// // Actions for statements manipulating pointer variables and pointer fields %action Set_Null_L(lhs) { 57 %t lhs + " = NULL" { lhs(v) = } } %action Copy_Var_L(lhs, rhs) { %t lhs + " = " + rhs %f { rhs(v) } { lhs(v) = rhs(v) } } %action Malloc_L(lhs) { %t lhs + " = (L) malloc(sizeof(struct node)) " %new { lhs(v) = isNew(v) t[n](v_1, v_2) = (isNew(v_1) ? v_1 == v_2 : t[n](v_1, v_2)) r[n, lhs](v) = isNew(v) foreach(z in PVar-{lhs}) { r[n,z](v) = r[n,z](v) } is[n](v) = is[n](v) } } %action Free_L(lhs) { 58 %t "free(" + lhs + ")" %f { lhs(v) } %message (E(v, v_1) lhs(v) & n(v, v_1)) -> "Internal Error! " + lhs + "->" + n + " != NULL" { t[n](v_1, v_2) = t[n](v_1, v_2) foreach(z in PVar) { r[n,z](v) = r[n,z](v) } is[n](v) = is[n](v) } %retain !lhs(v) } %action Get_Next_L(lhs, rhs) { %t lhs + " = " + rhs + "->" + n %f { E(v_1, v_2) rhs(v_1) & n(v_1, v_2) & t[n](v_2, v) } %message (!E(v) rhs(v)) -> "Illegal dereference to\n" + n + " component of " + rhs { lhs(v) = E(v_1) rhs(v_1) & n(v_1, v) } } %action Set_Next_Null_L(lhs) { %t lhs + "->" + n + " = NULL" %f { lhs(v), // optimized change-formula for t[n] update-formula 59 E(v_1, v_2) lhs(v_1) & n(v_1, v_2) & t[n](v_2, v) } %message (!E(v) lhs(v)) -> "Illegal dereference to\n" + n + " component of " + lhs { n(v_1, v_2) = n(v_1, v_2) & !lhs(v_1) } } %action Set_Next_L(lhs, rhs) { %t lhs + "->" + n + " = " + rhs %f { lhs(v), rhs(v), // optimized change-formula for t[n] upate-formula E(v_4) rhs(v_4) & t[n](v_4, v_2) } %message (E(v_1, v_2) lhs(v_1) & n(v_1, v_2)) -> "Internal Error! " + lhs + "->" + n + " != NULL" %message (E(v_1, v_2) lhs(v_1) & rhs(v_2) & t[n](v_2, v_1)) -> "A cycle may be introduced\nby assignment " + lhs + "->" + n + "=" + rhs { n(v_1, v_2) = n(v_1, v_2) | lhs(v_1) & rhs(v_2) } } ////////////////////////////////////////////////////////////////// // Actions needed to simulate program conditions involving pointer 60 // equality tests %action Is_Not_Null_Var(lhs) { %t lhs + " != NULL" %f { lhs(v) } %p E(v) lhs(v) } %action Is_Null_Var(lhs) { %t lhs + " == NULL" %f { lhs(v) } %p !(E(v) lhs(v)) } %action Is_Eq_Var(lhs, rhs) { %t lhs + " == " + rhs %f { lhs(v), rhs(v) } %p A(v) lhs(v) rhs(v) } %action Is_Not_Eq_Var(lhs, rhs) { %t lhs + " != " + rhs %f { lhs(v), rhs(v) } %p !A(v) lhs(v) rhs(v) } ///////////////////////////////////////// // Actions for testing various properties 61 %action Assert_ListInvariants(lhs) { %t "assertListInvariants(" + lhs + ")" %f { lhs(v) } %p E(v) r[n,lhs](v) & is[n](v) %message ( E(v) r[n,lhs](v) & is[n](v) ) -> "The list pointed by " + lhs + " may be shared!" } %action Assert_No_Leak(lhs) { %t "assertNoLeak(" + lhs + ")" %f { lhs(v) } %p E(v) !r[n,lhs](v) %message ( E(v) !r[n,lhs](v) ) -> "There may be a list element not reachable from variable " + lhs + "!" } Các định nghĩa chức trừu tƣợng predicates.tvp // For every program variable z there is a unary predicate that holds for // list elements pointed by z // The unique property is used to convey the fact that the predicate can hold // for at most one individual // The pointer property is a visualization hint for graphical renderers foreach (z in PVar) { %p z(v_1) unique pointer } 62 // The predicate n represents the n field of the list data type %p n(v_1, v_2) function acyclic ///////////////////////////////////////////// // Instrumentation (i.e., derived) predicates // The is[n] predicate holds for list elements pointed by two different // list elements %i is[n](v) = E(v_1, v_2) (v_1 != v_2 & n(v_1, v) & n(v_2, v)) // The t[n] predicate records transitive reflexive reachability between // list elements along the n field %i t[n](v_1, v_2) = n*(v_1, v_2) transitive reflexive // Integrity constraints for transitive reachability %r !t[n](v_1, v_2) ==> !n(v_1, v_2) %r !t[n](v_1, v_2) ==> v_1 != v_2 %r E(v_1) (t[n](v_1, v_2) & t[n](v_1, v_3) & !t[n](v_2, v_3)) ==> t[n](v_3, v_2) // For every program variable z the predicate r[n,z] holds for individual // v when v is reachable from variable z along the n field (more formally, // the corresponding list element is reachable from z) foreach (z in PVar) { %i r[n,z](v) = E(v_1) (z(v_1) & t[n](v_1, v)) } 63 Dữ liệu đầu vào TVLA phân tích chức tạo danh sách liên kết creat.tvp /////// // Sets %s PVar {x, f} #include "predicates.tvp" %% #include "actions.tvp" %% ///////////////////////////////////////////////////////////////////////// // Transition system for a function that creates new list of elements and // appends them to the list pointed by x L1 uninterpreted() L2 // for (i=0; in = NULL; L4 Set_Next_L(f, x) L5 // f->n = x; L5 Copy_Var_L(x, f) L1 // x = f; // } exit Assert_ListInvariants(x) error exit Assert_No_Leak(x) error 64 ... mềm, phân tích tĩnh chƣơng trình Chƣơng tìm hiểu sâu giải thích trừu tƣợng phân tích tĩnh chƣơng trình giải thích trừu tƣợng 18 CHƢƠNG II PHƢƠNG PHÁP GIẢI THÍCH TRỪU TƢỢNG 2.1 Khái niệm giải thích. .. lệnh việc giải thích trừu tƣợng thuộc tính cú pháp - Phân tích tĩnh chƣơng trình: lĩnh vực đƣợc ứng dụng nhiều giải thích trừu tƣợng Phƣơng pháp phân tích mã nguồn chƣơng trình cách trừu tƣợng... phân tích tĩnh chƣơng trình kỹ thuật giải thích trừu tƣợng, ứng dụng giải thích trừu tƣợng, kỹ thuật phân tích tĩnh chƣơng trình sử dụng giải thích trừu tƣợng thuật tốn phân tích mối quan hệ vơ

Ngày đăng: 26/03/2021, 06:58

TÀI LIỆU CÙNG NGƯỜI DÙNG

TÀI LIỆU LIÊN QUAN

w