├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── php-chain
├── .dockerignore
├── Dockerfile
├── Dockerfile.dev
├── analyze.php
├── build_chains.php
├── composer.json
├── config.php
├── count_metrics.php
├── lib
│ ├── AstVisitor
│ │ ├── ArrayAccessResolver.php
│ │ ├── Collector.php
│ │ └── LoopResolver.php
│ ├── Chain.php
│ ├── ChainBuilder.php
│ ├── ChainIterator.php
│ ├── ChainTree.php
│ ├── ClassLike.php
│ ├── ClassMethod.php
│ ├── Class_.php
│ ├── Dfg.php
│ ├── ExprCall.php
│ ├── ExprCall
│ │ ├── FuncCall.php
│ │ └── MethodCall.php
│ ├── FindBlockAndOP.php
│ ├── FunctionLike.php
│ ├── Function_.php
│ ├── Interface_.php
│ ├── Parser.php
│ ├── ProjectKnowledge.php
│ ├── TargetOp.php
│ ├── TargetParent.php
│ ├── TargetVar.php
│ ├── Trait_.php
│ ├── TreeAnalyzer.php
│ ├── TreePrinter.php
│ └── TreePrinter
│ │ └── GraphvizPrinter.php
├── load_chains.php
├── parse.php
└── test-project
│ ├── classes.php
│ ├── functions.php
│ └── interfaces.php
└── run.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | res
2 | vendor
3 | .idea
4 | php-chain/composer.lock
5 | notes
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU AFFERO GENERAL PUBLIC LICENSE
2 | Version 3, 19 November 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU Affero General Public License is a free, copyleft license for
11 | software and other kinds of works, specifically designed to ensure
12 | cooperation with the community in the case of network server software.
13 |
14 | The licenses for most software and other practical works are designed
15 | to take away your freedom to share and change the works. By contrast,
16 | our General Public Licenses are intended to guarantee your freedom to
17 | share and change all versions of a program--to make sure it remains free
18 | software for all its users.
19 |
20 | When we speak of free software, we are referring to freedom, not
21 | price. Our General Public Licenses are designed to make sure that you
22 | have the freedom to distribute copies of free software (and charge for
23 | them if you wish), that you receive source code or can get it if you
24 | want it, that you can change the software or use pieces of it in new
25 | free programs, and that you know you can do these things.
26 |
27 | Developers that use our General Public Licenses protect your rights
28 | with two steps: (1) assert copyright on the software, and (2) offer
29 | you this License which gives you legal permission to copy, distribute
30 | and/or modify the software.
31 |
32 | A secondary benefit of defending all users' freedom is that
33 | improvements made in alternate versions of the program, if they
34 | receive widespread use, become available for other developers to
35 | incorporate. Many developers of free software are heartened and
36 | encouraged by the resulting cooperation. However, in the case of
37 | software used on network servers, this result may fail to come about.
38 | The GNU General Public License permits making a modified version and
39 | letting the public access it on a server without ever releasing its
40 | source code to the public.
41 |
42 | The GNU Affero General Public License is designed specifically to
43 | ensure that, in such cases, the modified source code becomes available
44 | to the community. It requires the operator of a network server to
45 | provide the source code of the modified version running there to the
46 | users of that server. Therefore, public use of a modified version, on
47 | a publicly accessible server, gives the public access to the source
48 | code of the modified version.
49 |
50 | An older license, called the Affero General Public License and
51 | published by Affero, was designed to accomplish similar goals. This is
52 | a different license, not a version of the Affero GPL, but Affero has
53 | released a new version of the Affero GPL which permits relicensing under
54 | this license.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | TERMS AND CONDITIONS
60 |
61 | 0. Definitions.
62 |
63 | "This License" refers to version 3 of the GNU Affero General Public License.
64 |
65 | "Copyright" also means copyright-like laws that apply to other kinds of
66 | works, such as semiconductor masks.
67 |
68 | "The Program" refers to any copyrightable work licensed under this
69 | License. Each licensee is addressed as "you". "Licensees" and
70 | "recipients" may be individuals or organizations.
71 |
72 | To "modify" a work means to copy from or adapt all or part of the work
73 | in a fashion requiring copyright permission, other than the making of an
74 | exact copy. The resulting work is called a "modified version" of the
75 | earlier work or a work "based on" the earlier work.
76 |
77 | A "covered work" means either the unmodified Program or a work based
78 | on the Program.
79 |
80 | To "propagate" a work means to do anything with it that, without
81 | permission, would make you directly or secondarily liable for
82 | infringement under applicable copyright law, except executing it on a
83 | computer or modifying a private copy. Propagation includes copying,
84 | distribution (with or without modification), making available to the
85 | public, and in some countries other activities as well.
86 |
87 | To "convey" a work means any kind of propagation that enables other
88 | parties to make or receive copies. Mere interaction with a user through
89 | a computer network, with no transfer of a copy, is not conveying.
90 |
91 | An interactive user interface displays "Appropriate Legal Notices"
92 | to the extent that it includes a convenient and prominently visible
93 | feature that (1) displays an appropriate copyright notice, and (2)
94 | tells the user that there is no warranty for the work (except to the
95 | extent that warranties are provided), that licensees may convey the
96 | work under this License, and how to view a copy of this License. If
97 | the interface presents a list of user commands or options, such as a
98 | menu, a prominent item in the list meets this criterion.
99 |
100 | 1. Source Code.
101 |
102 | The "source code" for a work means the preferred form of the work
103 | for making modifications to it. "Object code" means any non-source
104 | form of a work.
105 |
106 | A "Standard Interface" means an interface that either is an official
107 | standard defined by a recognized standards body, or, in the case of
108 | interfaces specified for a particular programming language, one that
109 | is widely used among developers working in that language.
110 |
111 | The "System Libraries" of an executable work include anything, other
112 | than the work as a whole, that (a) is included in the normal form of
113 | packaging a Major Component, but which is not part of that Major
114 | Component, and (b) serves only to enable use of the work with that
115 | Major Component, or to implement a Standard Interface for which an
116 | implementation is available to the public in source code form. A
117 | "Major Component", in this context, means a major essential component
118 | (kernel, window system, and so on) of the specific operating system
119 | (if any) on which the executable work runs, or a compiler used to
120 | produce the work, or an object code interpreter used to run it.
121 |
122 | The "Corresponding Source" for a work in object code form means all
123 | the source code needed to generate, install, and (for an executable
124 | work) run the object code and to modify the work, including scripts to
125 | control those activities. However, it does not include the work's
126 | System Libraries, or general-purpose tools or generally available free
127 | programs which are used unmodified in performing those activities but
128 | which are not part of the work. For example, Corresponding Source
129 | includes interface definition files associated with source files for
130 | the work, and the source code for shared libraries and dynamically
131 | linked subprograms that the work is specifically designed to require,
132 | such as by intimate data communication or control flow between those
133 | subprograms and other parts of the work.
134 |
135 | The Corresponding Source need not include anything that users
136 | can regenerate automatically from other parts of the Corresponding
137 | Source.
138 |
139 | The Corresponding Source for a work in source code form is that
140 | same work.
141 |
142 | 2. Basic Permissions.
143 |
144 | All rights granted under this License are granted for the term of
145 | copyright on the Program, and are irrevocable provided the stated
146 | conditions are met. This License explicitly affirms your unlimited
147 | permission to run the unmodified Program. The output from running a
148 | covered work is covered by this License only if the output, given its
149 | content, constitutes a covered work. This License acknowledges your
150 | rights of fair use or other equivalent, as provided by copyright law.
151 |
152 | You may make, run and propagate covered works that you do not
153 | convey, without conditions so long as your license otherwise remains
154 | in force. You may convey covered works to others for the sole purpose
155 | of having them make modifications exclusively for you, or provide you
156 | with facilities for running those works, provided that you comply with
157 | the terms of this License in conveying all material for which you do
158 | not control copyright. Those thus making or running the covered works
159 | for you must do so exclusively on your behalf, under your direction
160 | and control, on terms that prohibit them from making any copies of
161 | your copyrighted material outside their relationship with you.
162 |
163 | Conveying under any other circumstances is permitted solely under
164 | the conditions stated below. Sublicensing is not allowed; section 10
165 | makes it unnecessary.
166 |
167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
168 |
169 | No covered work shall be deemed part of an effective technological
170 | measure under any applicable law fulfilling obligations under article
171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
172 | similar laws prohibiting or restricting circumvention of such
173 | measures.
174 |
175 | When you convey a covered work, you waive any legal power to forbid
176 | circumvention of technological measures to the extent such circumvention
177 | is effected by exercising rights under this License with respect to
178 | the covered work, and you disclaim any intention to limit operation or
179 | modification of the work as a means of enforcing, against the work's
180 | users, your or third parties' legal rights to forbid circumvention of
181 | technological measures.
182 |
183 | 4. Conveying Verbatim Copies.
184 |
185 | You may convey verbatim copies of the Program's source code as you
186 | receive it, in any medium, provided that you conspicuously and
187 | appropriately publish on each copy an appropriate copyright notice;
188 | keep intact all notices stating that this License and any
189 | non-permissive terms added in accord with section 7 apply to the code;
190 | keep intact all notices of the absence of any warranty; and give all
191 | recipients a copy of this License along with the Program.
192 |
193 | You may charge any price or no price for each copy that you convey,
194 | and you may offer support or warranty protection for a fee.
195 |
196 | 5. Conveying Modified Source Versions.
197 |
198 | You may convey a work based on the Program, or the modifications to
199 | produce it from the Program, in the form of source code under the
200 | terms of section 4, provided that you also meet all of these conditions:
201 |
202 | a) The work must carry prominent notices stating that you modified
203 | it, and giving a relevant date.
204 |
205 | b) The work must carry prominent notices stating that it is
206 | released under this License and any conditions added under section
207 | 7. This requirement modifies the requirement in section 4 to
208 | "keep intact all notices".
209 |
210 | c) You must license the entire work, as a whole, under this
211 | License to anyone who comes into possession of a copy. This
212 | License will therefore apply, along with any applicable section 7
213 | additional terms, to the whole of the work, and all its parts,
214 | regardless of how they are packaged. This License gives no
215 | permission to license the work in any other way, but it does not
216 | invalidate such permission if you have separately received it.
217 |
218 | d) If the work has interactive user interfaces, each must display
219 | Appropriate Legal Notices; however, if the Program has interactive
220 | interfaces that do not display Appropriate Legal Notices, your
221 | work need not make them do so.
222 |
223 | A compilation of a covered work with other separate and independent
224 | works, which are not by their nature extensions of the covered work,
225 | and which are not combined with it such as to form a larger program,
226 | in or on a volume of a storage or distribution medium, is called an
227 | "aggregate" if the compilation and its resulting copyright are not
228 | used to limit the access or legal rights of the compilation's users
229 | beyond what the individual works permit. Inclusion of a covered work
230 | in an aggregate does not cause this License to apply to the other
231 | parts of the aggregate.
232 |
233 | 6. Conveying Non-Source Forms.
234 |
235 | You may convey a covered work in object code form under the terms
236 | of sections 4 and 5, provided that you also convey the
237 | machine-readable Corresponding Source under the terms of this License,
238 | in one of these ways:
239 |
240 | a) Convey the object code in, or embodied in, a physical product
241 | (including a physical distribution medium), accompanied by the
242 | Corresponding Source fixed on a durable physical medium
243 | customarily used for software interchange.
244 |
245 | b) Convey the object code in, or embodied in, a physical product
246 | (including a physical distribution medium), accompanied by a
247 | written offer, valid for at least three years and valid for as
248 | long as you offer spare parts or customer support for that product
249 | model, to give anyone who possesses the object code either (1) a
250 | copy of the Corresponding Source for all the software in the
251 | product that is covered by this License, on a durable physical
252 | medium customarily used for software interchange, for a price no
253 | more than your reasonable cost of physically performing this
254 | conveying of source, or (2) access to copy the
255 | Corresponding Source from a network server at no charge.
256 |
257 | c) Convey individual copies of the object code with a copy of the
258 | written offer to provide the Corresponding Source. This
259 | alternative is allowed only occasionally and noncommercially, and
260 | only if you received the object code with such an offer, in accord
261 | with subsection 6b.
262 |
263 | d) Convey the object code by offering access from a designated
264 | place (gratis or for a charge), and offer equivalent access to the
265 | Corresponding Source in the same way through the same place at no
266 | further charge. You need not require recipients to copy the
267 | Corresponding Source along with the object code. If the place to
268 | copy the object code is a network server, the Corresponding Source
269 | may be on a different server (operated by you or a third party)
270 | that supports equivalent copying facilities, provided you maintain
271 | clear directions next to the object code saying where to find the
272 | Corresponding Source. Regardless of what server hosts the
273 | Corresponding Source, you remain obligated to ensure that it is
274 | available for as long as needed to satisfy these requirements.
275 |
276 | e) Convey the object code using peer-to-peer transmission, provided
277 | you inform other peers where the object code and Corresponding
278 | Source of the work are being offered to the general public at no
279 | charge under subsection 6d.
280 |
281 | A separable portion of the object code, whose source code is excluded
282 | from the Corresponding Source as a System Library, need not be
283 | included in conveying the object code work.
284 |
285 | A "User Product" is either (1) a "consumer product", which means any
286 | tangible personal property which is normally used for personal, family,
287 | or household purposes, or (2) anything designed or sold for incorporation
288 | into a dwelling. In determining whether a product is a consumer product,
289 | doubtful cases shall be resolved in favor of coverage. For a particular
290 | product received by a particular user, "normally used" refers to a
291 | typical or common use of that class of product, regardless of the status
292 | of the particular user or of the way in which the particular user
293 | actually uses, or expects or is expected to use, the product. A product
294 | is a consumer product regardless of whether the product has substantial
295 | commercial, industrial or non-consumer uses, unless such uses represent
296 | the only significant mode of use of the product.
297 |
298 | "Installation Information" for a User Product means any methods,
299 | procedures, authorization keys, or other information required to install
300 | and execute modified versions of a covered work in that User Product from
301 | a modified version of its Corresponding Source. The information must
302 | suffice to ensure that the continued functioning of the modified object
303 | code is in no case prevented or interfered with solely because
304 | modification has been made.
305 |
306 | If you convey an object code work under this section in, or with, or
307 | specifically for use in, a User Product, and the conveying occurs as
308 | part of a transaction in which the right of possession and use of the
309 | User Product is transferred to the recipient in perpetuity or for a
310 | fixed term (regardless of how the transaction is characterized), the
311 | Corresponding Source conveyed under this section must be accompanied
312 | by the Installation Information. But this requirement does not apply
313 | if neither you nor any third party retains the ability to install
314 | modified object code on the User Product (for example, the work has
315 | been installed in ROM).
316 |
317 | The requirement to provide Installation Information does not include a
318 | requirement to continue to provide support service, warranty, or updates
319 | for a work that has been modified or installed by the recipient, or for
320 | the User Product in which it has been modified or installed. Access to a
321 | network may be denied when the modification itself materially and
322 | adversely affects the operation of the network or violates the rules and
323 | protocols for communication across the network.
324 |
325 | Corresponding Source conveyed, and Installation Information provided,
326 | in accord with this section must be in a format that is publicly
327 | documented (and with an implementation available to the public in
328 | source code form), and must require no special password or key for
329 | unpacking, reading or copying.
330 |
331 | 7. Additional Terms.
332 |
333 | "Additional permissions" are terms that supplement the terms of this
334 | License by making exceptions from one or more of its conditions.
335 | Additional permissions that are applicable to the entire Program shall
336 | be treated as though they were included in this License, to the extent
337 | that they are valid under applicable law. If additional permissions
338 | apply only to part of the Program, that part may be used separately
339 | under those permissions, but the entire Program remains governed by
340 | this License without regard to the additional permissions.
341 |
342 | When you convey a copy of a covered work, you may at your option
343 | remove any additional permissions from that copy, or from any part of
344 | it. (Additional permissions may be written to require their own
345 | removal in certain cases when you modify the work.) You may place
346 | additional permissions on material, added by you to a covered work,
347 | for which you have or can give appropriate copyright permission.
348 |
349 | Notwithstanding any other provision of this License, for material you
350 | add to a covered work, you may (if authorized by the copyright holders of
351 | that material) supplement the terms of this License with terms:
352 |
353 | a) Disclaiming warranty or limiting liability differently from the
354 | terms of sections 15 and 16 of this License; or
355 |
356 | b) Requiring preservation of specified reasonable legal notices or
357 | author attributions in that material or in the Appropriate Legal
358 | Notices displayed by works containing it; or
359 |
360 | c) Prohibiting misrepresentation of the origin of that material, or
361 | requiring that modified versions of such material be marked in
362 | reasonable ways as different from the original version; or
363 |
364 | d) Limiting the use for publicity purposes of names of licensors or
365 | authors of the material; or
366 |
367 | e) Declining to grant rights under trademark law for use of some
368 | trade names, trademarks, or service marks; or
369 |
370 | f) Requiring indemnification of licensors and authors of that
371 | material by anyone who conveys the material (or modified versions of
372 | it) with contractual assumptions of liability to the recipient, for
373 | any liability that these contractual assumptions directly impose on
374 | those licensors and authors.
375 |
376 | All other non-permissive additional terms are considered "further
377 | restrictions" within the meaning of section 10. If the Program as you
378 | received it, or any part of it, contains a notice stating that it is
379 | governed by this License along with a term that is a further
380 | restriction, you may remove that term. If a license document contains
381 | a further restriction but permits relicensing or conveying under this
382 | License, you may add to a covered work material governed by the terms
383 | of that license document, provided that the further restriction does
384 | not survive such relicensing or conveying.
385 |
386 | If you add terms to a covered work in accord with this section, you
387 | must place, in the relevant source files, a statement of the
388 | additional terms that apply to those files, or a notice indicating
389 | where to find the applicable terms.
390 |
391 | Additional terms, permissive or non-permissive, may be stated in the
392 | form of a separately written license, or stated as exceptions;
393 | the above requirements apply either way.
394 |
395 | 8. Termination.
396 |
397 | You may not propagate or modify a covered work except as expressly
398 | provided under this License. Any attempt otherwise to propagate or
399 | modify it is void, and will automatically terminate your rights under
400 | this License (including any patent licenses granted under the third
401 | paragraph of section 11).
402 |
403 | However, if you cease all violation of this License, then your
404 | license from a particular copyright holder is reinstated (a)
405 | provisionally, unless and until the copyright holder explicitly and
406 | finally terminates your license, and (b) permanently, if the copyright
407 | holder fails to notify you of the violation by some reasonable means
408 | prior to 60 days after the cessation.
409 |
410 | Moreover, your license from a particular copyright holder is
411 | reinstated permanently if the copyright holder notifies you of the
412 | violation by some reasonable means, this is the first time you have
413 | received notice of violation of this License (for any work) from that
414 | copyright holder, and you cure the violation prior to 30 days after
415 | your receipt of the notice.
416 |
417 | Termination of your rights under this section does not terminate the
418 | licenses of parties who have received copies or rights from you under
419 | this License. If your rights have been terminated and not permanently
420 | reinstated, you do not qualify to receive new licenses for the same
421 | material under section 10.
422 |
423 | 9. Acceptance Not Required for Having Copies.
424 |
425 | You are not required to accept this License in order to receive or
426 | run a copy of the Program. Ancillary propagation of a covered work
427 | occurring solely as a consequence of using peer-to-peer transmission
428 | to receive a copy likewise does not require acceptance. However,
429 | nothing other than this License grants you permission to propagate or
430 | modify any covered work. These actions infringe copyright if you do
431 | not accept this License. Therefore, by modifying or propagating a
432 | covered work, you indicate your acceptance of this License to do so.
433 |
434 | 10. Automatic Licensing of Downstream Recipients.
435 |
436 | Each time you convey a covered work, the recipient automatically
437 | receives a license from the original licensors, to run, modify and
438 | propagate that work, subject to this License. You are not responsible
439 | for enforcing compliance by third parties with this License.
440 |
441 | An "entity transaction" is a transaction transferring control of an
442 | organization, or substantially all assets of one, or subdividing an
443 | organization, or merging organizations. If propagation of a covered
444 | work results from an entity transaction, each party to that
445 | transaction who receives a copy of the work also receives whatever
446 | licenses to the work the party's predecessor in interest had or could
447 | give under the previous paragraph, plus a right to possession of the
448 | Corresponding Source of the work from the predecessor in interest, if
449 | the predecessor has it or can get it with reasonable efforts.
450 |
451 | You may not impose any further restrictions on the exercise of the
452 | rights granted or affirmed under this License. For example, you may
453 | not impose a license fee, royalty, or other charge for exercise of
454 | rights granted under this License, and you may not initiate litigation
455 | (including a cross-claim or counterclaim in a lawsuit) alleging that
456 | any patent claim is infringed by making, using, selling, offering for
457 | sale, or importing the Program or any portion of it.
458 |
459 | 11. Patents.
460 |
461 | A "contributor" is a copyright holder who authorizes use under this
462 | License of the Program or a work on which the Program is based. The
463 | work thus licensed is called the contributor's "contributor version".
464 |
465 | A contributor's "essential patent claims" are all patent claims
466 | owned or controlled by the contributor, whether already acquired or
467 | hereafter acquired, that would be infringed by some manner, permitted
468 | by this License, of making, using, or selling its contributor version,
469 | but do not include claims that would be infringed only as a
470 | consequence of further modification of the contributor version. For
471 | purposes of this definition, "control" includes the right to grant
472 | patent sublicenses in a manner consistent with the requirements of
473 | this License.
474 |
475 | Each contributor grants you a non-exclusive, worldwide, royalty-free
476 | patent license under the contributor's essential patent claims, to
477 | make, use, sell, offer for sale, import and otherwise run, modify and
478 | propagate the contents of its contributor version.
479 |
480 | In the following three paragraphs, a "patent license" is any express
481 | agreement or commitment, however denominated, not to enforce a patent
482 | (such as an express permission to practice a patent or covenant not to
483 | sue for patent infringement). To "grant" such a patent license to a
484 | party means to make such an agreement or commitment not to enforce a
485 | patent against the party.
486 |
487 | If you convey a covered work, knowingly relying on a patent license,
488 | and the Corresponding Source of the work is not available for anyone
489 | to copy, free of charge and under the terms of this License, through a
490 | publicly available network server or other readily accessible means,
491 | then you must either (1) cause the Corresponding Source to be so
492 | available, or (2) arrange to deprive yourself of the benefit of the
493 | patent license for this particular work, or (3) arrange, in a manner
494 | consistent with the requirements of this License, to extend the patent
495 | license to downstream recipients. "Knowingly relying" means you have
496 | actual knowledge that, but for the patent license, your conveying the
497 | covered work in a country, or your recipient's use of the covered work
498 | in a country, would infringe one or more identifiable patents in that
499 | country that you have reason to believe are valid.
500 |
501 | If, pursuant to or in connection with a single transaction or
502 | arrangement, you convey, or propagate by procuring conveyance of, a
503 | covered work, and grant a patent license to some of the parties
504 | receiving the covered work authorizing them to use, propagate, modify
505 | or convey a specific copy of the covered work, then the patent license
506 | you grant is automatically extended to all recipients of the covered
507 | work and works based on it.
508 |
509 | A patent license is "discriminatory" if it does not include within
510 | the scope of its coverage, prohibits the exercise of, or is
511 | conditioned on the non-exercise of one or more of the rights that are
512 | specifically granted under this License. You may not convey a covered
513 | work if you are a party to an arrangement with a third party that is
514 | in the business of distributing software, under which you make payment
515 | to the third party based on the extent of your activity of conveying
516 | the work, and under which the third party grants, to any of the
517 | parties who would receive the covered work from you, a discriminatory
518 | patent license (a) in connection with copies of the covered work
519 | conveyed by you (or copies made from those copies), or (b) primarily
520 | for and in connection with specific products or compilations that
521 | contain the covered work, unless you entered into that arrangement,
522 | or that patent license was granted, prior to 28 March 2007.
523 |
524 | Nothing in this License shall be construed as excluding or limiting
525 | any implied license or other defenses to infringement that may
526 | otherwise be available to you under applicable patent law.
527 |
528 | 12. No Surrender of Others' Freedom.
529 |
530 | If conditions are imposed on you (whether by court order, agreement or
531 | otherwise) that contradict the conditions of this License, they do not
532 | excuse you from the conditions of this License. If you cannot convey a
533 | covered work so as to satisfy simultaneously your obligations under this
534 | License and any other pertinent obligations, then as a consequence you may
535 | not convey it at all. For example, if you agree to terms that obligate you
536 | to collect a royalty for further conveying from those to whom you convey
537 | the Program, the only way you could satisfy both those terms and this
538 | License would be to refrain entirely from conveying the Program.
539 |
540 | 13. Remote Network Interaction; Use with the GNU General Public License.
541 |
542 | Notwithstanding any other provision of this License, if you modify the
543 | Program, your modified version must prominently offer all users
544 | interacting with it remotely through a computer network (if your version
545 | supports such interaction) an opportunity to receive the Corresponding
546 | Source of your version by providing access to the Corresponding Source
547 | from a network server at no charge, through some standard or customary
548 | means of facilitating copying of software. This Corresponding Source
549 | shall include the Corresponding Source for any work covered by version 3
550 | of the GNU General Public License that is incorporated pursuant to the
551 | following paragraph.
552 |
553 | Notwithstanding any other provision of this License, you have
554 | permission to link or combine any covered work with a work licensed
555 | under version 3 of the GNU General Public License into a single
556 | combined work, and to convey the resulting work. The terms of this
557 | License will continue to apply to the part which is the covered work,
558 | but the work with which it is combined will remain governed by version
559 | 3 of the GNU General Public License.
560 |
561 | 14. Revised Versions of this License.
562 |
563 | The Free Software Foundation may publish revised and/or new versions of
564 | the GNU Affero General Public License from time to time. Such new versions
565 | will be similar in spirit to the present version, but may differ in detail to
566 | address new problems or concerns.
567 |
568 | Each version is given a distinguishing version number. If the
569 | Program specifies that a certain numbered version of the GNU Affero General
570 | Public License "or any later version" applies to it, you have the
571 | option of following the terms and conditions either of that numbered
572 | version or of any later version published by the Free Software
573 | Foundation. If the Program does not specify a version number of the
574 | GNU Affero General Public License, you may choose any version ever published
575 | by the Free Software Foundation.
576 |
577 | If the Program specifies that a proxy can decide which future
578 | versions of the GNU Affero General Public License can be used, that proxy's
579 | public statement of acceptance of a version permanently authorizes you
580 | to choose that version for the Program.
581 |
582 | Later license versions may give you additional or different
583 | permissions. However, no additional obligations are imposed on any
584 | author or copyright holder as a result of your choosing to follow a
585 | later version.
586 |
587 | 15. Disclaimer of Warranty.
588 |
589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
597 |
598 | 16. Limitation of Liability.
599 |
600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
608 | SUCH DAMAGES.
609 |
610 | 17. Interpretation of Sections 15 and 16.
611 |
612 | If the disclaimer of warranty and limitation of liability provided
613 | above cannot be given local legal effect according to their terms,
614 | reviewing courts shall apply local law that most closely approximates
615 | an absolute waiver of all civil liability in connection with the
616 | Program, unless a warranty or assumption of liability accompanies a
617 | copy of the Program in return for a fee.
618 |
619 | END OF TERMS AND CONDITIONS
620 |
621 | How to Apply These Terms to Your New Programs
622 |
623 | If you develop a new program, and you want it to be of the greatest
624 | possible use to the public, the best way to achieve this is to make it
625 | free software which everyone can redistribute and change under these terms.
626 |
627 | To do so, attach the following notices to the program. It is safest
628 | to attach them to the start of each source file to most effectively
629 | state the exclusion of warranty; and each file should have at least
630 | the "copyright" line and a pointer to where the full notice is found.
631 |
632 | Copyright (C) Dmitry Pavlov, Diana Kovalenko
633 |
634 | This program is free software: you can redistribute it and/or modify
635 | it under the terms of the GNU Affero General Public License as published
636 | by the Free Software Foundation, either version 3 of the License, or
637 | (at your option) any later version.
638 |
639 | This program is distributed in the hope that it will be useful,
640 | but WITHOUT ANY WARRANTY; without even the implied warranty of
641 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
642 | GNU Affero General Public License for more details.
643 |
644 | You should have received a copy of the GNU Affero General Public License
645 | along with this program. If not, see .
646 |
647 | Also add information on how to contact you by electronic and paper mail.
648 |
649 | If your software can interact with users remotely through a computer
650 | network, you should also make sure that it provides a way for users to
651 | get its source. For example, if your program is a web application, its
652 | interface could display a "Source" link that leads users to an archive
653 | of the code. There are many ways you could offer source, and different
654 | solutions will be better for different programs; see section 13 for the
655 | specific requirements.
656 |
657 | You should also get your employer (if you work as a programmer) or school,
658 | if any, to sign a "copyright disclaimer" for the program, if necessary.
659 | For more information on this, and how to apply and follow the GNU AGPL, see
660 | .
661 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: all
2 | all: build
3 |
4 | .PHONY: dev
5 | dev: dev_build
6 | docker run -ti --rm -v `pwd`/res:/res -v `pwd`/php-chain/lib:/php-chain/lib php-chain_dev bash
7 |
8 | .PHONY: dev_build
9 | dev_build:
10 | docker build -f php-chain/Dockerfile.dev -t php-chain_dev php-chain
11 |
12 | .PHONY: build
13 | build:
14 | docker build -t php-chain php-chain
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # php-chain
2 |
3 | This tool was written for search of chains in PHP projects within the [master's thesis](https://blog.dsp25no.ru/2019/05/29/could-you-find-some-chains).
4 |
5 | ## Quick start
6 |
7 | To analyze php-project, run:
8 |
9 | ./run.sh analyze
10 |
11 | Results will be in the directory `res`.
12 |
13 | For more options, run:
14 |
15 | ./run.sh help
16 |
17 | ## Quick developing start
18 |
19 | To start tool's container for testing, run:
20 |
21 | make dev
22 |
--------------------------------------------------------------------------------
/php-chain/.dockerignore:
--------------------------------------------------------------------------------
1 | composer.lock
2 | vendor
3 |
--------------------------------------------------------------------------------
/php-chain/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM composer:1.8
2 | LABEL authors="Diana Kovalenko ,\
3 | Dmitry Pavlov "
4 |
5 | WORKDIR /php-chain
6 | COPY composer.json .
7 | RUN composer install --no-dev
8 |
9 | COPY . .
10 |
11 | ENTRYPOINT ["php", "/php-chain/analyze.php"]
12 |
--------------------------------------------------------------------------------
/php-chain/Dockerfile.dev:
--------------------------------------------------------------------------------
1 | FROM composer:1.8
2 | LABEL authors="Diana Kovalenko ,\
3 | Dmitry Pavlov "
4 |
5 | RUN apk add autoconf g++ make && \
6 | pecl install xdebug && \
7 | docker-php-ext-enable xdebug
8 |
9 | RUN echo "xdebug.max_nesting_level=-1" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
10 |
11 | WORKDIR /php-chain
12 | COPY composer.json .
13 | RUN composer install
14 |
15 | COPY test-project /target
16 |
17 | COPY lib lib
18 | COPY analyze.php .
19 | COPY config.php .
20 |
--------------------------------------------------------------------------------
/php-chain/analyze.php:
--------------------------------------------------------------------------------
1 | findAllChains($config["depth"]);
8 | echo "Search finished".PHP_EOL;
9 | $file = fopen("/res/chains", "w");
10 | fwrite($file, json_encode($chainTree, JSON_PRETTY_PRINT));
11 | fclose($file);
12 | $file = fopen("/res/chains_serialized", "w");
13 | fwrite($file, serialize($chainTree));
14 | fclose($file);
--------------------------------------------------------------------------------
/php-chain/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "autoload": {
3 | "psr-4": {
4 | "PhpChain\\" : "lib/"
5 | }
6 | },
7 | "require": {
8 | "nikic/php-parser": "^4.0",
9 | "ircmaxell/php-cfg": "v1.0.x-dev",
10 | "ext-json": "*"
11 | },
12 | "require-dev": {
13 | "psy/psysh": "@stable"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/php-chain/config.php:
--------------------------------------------------------------------------------
1 | ["unlink", "exec", "expect_popen", "passthru", "pcntl_exec", "popen", "proc_open", "shell_exec", "system", "file_put_contents", "fwrite", "rmdir", "call_user_func", "call_user_func_array", "mail", "eval", "assert"],
6 | "magic" => ["__destruct", "__wakeup"],
7 | "depth" => 8,
8 | "functions" => [
9 | "call_user_func" => ["params" => 2],
10 | "system" => ["params" => 1],
11 | "unlink" => ["params" => 1],
12 | "exec" => ["params" => 1],
13 | "expect_popen" => ["params" => 1],
14 | "passthru" => ["params" => 1],
15 | "pcntl_exec" => ["params" => 1],
16 | "popen" => ["params" => 1],
17 | "proc_open" => ["params" => 1],
18 | "shell_exec" => ["params" => 1],
19 | "file_put_contents" => ["params" => 2],
20 | "fwrite" => ["params" => 2],
21 | "rmdir" => ["params" => 1],
22 | "call_user_func_array" => ["params" => 2],
23 | "mail" => ["params" => 5],
24 | "eval" => ["params" => 1],
25 | "assert" => ["params" => 1]
26 | ],
27 | "features" => [
28 | "__call" => false,
29 | "foreach" => false,
30 | "offsetGet" => false
31 | ],
32 | "metrics" => true
33 | );
34 |
--------------------------------------------------------------------------------
/php-chain/count_metrics.php:
--------------------------------------------------------------------------------
1 | analyze();
13 | $file = fopen("/res/metric_chains", "w");
14 | fwrite($file, json_encode($chainTree, JSON_PRETTY_PRINT));
15 | fclose($file);
16 |
--------------------------------------------------------------------------------
/php-chain/lib/AstVisitor/ArrayAccessResolver.php:
--------------------------------------------------------------------------------
1 | getAttributes();
22 | if($node->dim) {
23 | $param = new ParserArg($node->dim);
24 | $param->setAttributes($attributes);
25 | $params = [$param];
26 | } else {
27 | $params = [];
28 | }
29 | return new ParserMethodCall(
30 | $node->var,
31 | "offsetGet",
32 | $params,
33 | $attributes
34 | );
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/php-chain/lib/AstVisitor/Collector.php:
--------------------------------------------------------------------------------
1 | knowledge = $knowledge;
25 | }
26 |
27 | public function enterNode(\PhpParser\Node $node)
28 | {
29 | if ($node instanceof ParserClassLike) {
30 | $class = ClassLike::create($node, $this->knowledge);
31 | $this->knowledge->addClass($class);
32 | $this->currentClass = $class;
33 | } elseif ($node instanceof ParserClassMethod) {
34 | $method = ClassMethod::create($node, $this->currentClass);
35 | $this->currentClass->addMethod($method);
36 | } elseif ($node instanceof ParserFunction) {
37 | $function = Function_::create($node);
38 | $this->knowledge->addFunction($function);
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/php-chain/lib/AstVisitor/LoopResolver.php:
--------------------------------------------------------------------------------
1 | getIterator();
26 | }
27 | for ($it->rewind(); $it->valid(); $it->next()) {
28 | $v = $it->current();
29 | $k = $it->key();
30 | ...
31 | }
32 | */
33 | $it = $node->expr;
34 | $v = $node->valueVar;
35 | $k = $node->keyVar;
36 | $attributes = $node->getAttributes();
37 | $result = [];
38 | $result[] = new ParserIf(
39 | new ParserInstanceof(
40 | $it,
41 | new Node\Name("\IteratorAgreggate", $attributes),
42 | $attributes
43 | ),
44 | ["stmts" => [
45 | new ParserAssign(
46 | $it,
47 | new ParserMethodCall($it, "getIterator", [], $attributes),
48 | $attributes
49 | )
50 | ]],
51 | $attributes
52 | );
53 | $result[] = new ParserMethodCall($it, "rewind", [], $attributes);
54 | $if = new ParserIf(
55 | new ParserMethodCall($it, "valid", [], $attributes),
56 | ["stmts" => [
57 | new ParserAssign(
58 | $v,
59 | new ParserMethodCall(
60 | $it,
61 | "current",
62 | [],
63 | $attributes),
64 | $attributes
65 | ),
66 | ]
67 | ]
68 | );
69 | if($k) {
70 | $if->stmts[] = new ParserAssign(
71 | $k,
72 | new ParserMethodCall($it, "key", [], $attributes),
73 | $attributes
74 | );
75 | }
76 | $if->stmts[] = new ParserMethodCall($it, "next", [], $attributes);
77 | $result[] = $if;
78 | return $result;
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/php-chain/lib/Chain.php:
--------------------------------------------------------------------------------
1 | function = $function;
15 | $this->call = $call;
16 | $this->next = NULL;
17 | $this->prev = NULL;
18 | }
19 |
20 | public function value()
21 | {
22 | return $this->function;
23 | }
24 |
25 | public function getCall()
26 | {
27 | return $this->call;
28 | }
29 |
30 | public function next()
31 | {
32 | return $this->next;
33 | }
34 |
35 | public function prev()
36 | {
37 | return $this->prev;
38 | }
39 |
40 | public function append($new_node, $call)
41 | {
42 | if (!$new_node instanceof Chain) {
43 | throw new \Exception('invalid new node'.PHP_EOL);
44 | }
45 | $item = $this->last();
46 | $item->call = $call;
47 | $item->next = & $new_node;
48 | $new_node->prev = & $item;
49 | return $item->next;
50 | }
51 |
52 | public function rewind()
53 | {
54 | $item = $this;
55 | while($item->prev){
56 | $item = $item->prev;
57 | }
58 | return $item;
59 | }
60 |
61 | public function last()
62 | {
63 | $item = $this;
64 | while($item->next){
65 | $item = $item->next;
66 | }
67 | return $item;
68 | }
69 |
70 | public function length() {
71 | $length = 1;
72 | $item = $this->rewind();
73 | while($item->next){
74 | $item = $item->next;
75 | $length++;
76 | }
77 | return $length;
78 | }
79 |
80 | public function copyTail()
81 | {
82 | $item = $this;
83 | $root = new Chain($item->function, $item->call);
84 | $result = $root;
85 | while ($item = $item->next){
86 | $new_node = new Chain($item->function, $item->call);
87 | $result->next = $new_node;
88 | $new_node->prev = $result;
89 | $result = $new_node;
90 | }
91 | return $root;
92 | }
93 |
94 | public function copyChain()
95 | {
96 | $root = $this->rewind();
97 | return $root->copyTail();
98 | }
99 |
100 | public function delLastNode()
101 | {
102 | $last = $this->last();
103 | if ($prev = $last->prev) {
104 | $prev->call = null;
105 | $prev->next = null;
106 | $last->prev = null;
107 | }
108 | return $prev;
109 | }
110 |
111 | public function delTail()
112 | {
113 | if($this->next) {
114 | $this->next->prev = null;
115 | $this->next = null;
116 | $this->call = null;
117 | }
118 | }
119 |
120 | public function isEqualTail($node)
121 | {
122 | return $this->function === $node->function and
123 | $this->call === $node->call and
124 | $this->next->isEqualTail($node->next);
125 | }
126 |
127 | public function __toString()
128 | {
129 | return strval($this->function) . ($this->next ? $this->call->node->getStartLine() . " => ".strval($this->next) : "");
130 | }
131 |
132 | public function __destruct()
133 | {
134 | unset($this->function);
135 | unset($this->next);
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/php-chain/lib/ChainBuilder.php:
--------------------------------------------------------------------------------
1 | system = $config["system"];
32 | $this->magic = $config["magic"];
33 | if(!$config["features"]["__call"]) {
34 | $this->not_call = true;
35 | }
36 | $this->knowledge = $knowledge;
37 | $this->system_inside = new \SplObjectStorage();
38 | $this->checked = new \SplObjectStorage();
39 | $this->chainTree = new ChainTree(new Function_("", null, null));
40 |
41 | $factory = new AstBuilder();
42 | foreach($this->system as $function_name) {
43 | $function = $factory->function($function_name);
44 | $params = [];
45 | for ($i = 0; $i < $config["functions"][$function_name]["params"]; $i++) {
46 | $params[] = $factory->param("p$i");
47 | }
48 | $function->addParams($params);
49 | $this->chainTree->addChildren(Function_::create($function->getNode()), new \StdClass);
50 | }
51 | }
52 |
53 | private function getSuffixChain($function, $maxLen)
54 | {
55 | foreach ($this->chainTree->getChainsStartFrom($function, $maxLen) as $chain) {
56 | yield $chain;
57 | }
58 | }
59 |
60 | /**
61 | * @param $function_like
62 | * @param $chain
63 | * @return \Generator : new chain
64 | * @throws \Exception
65 | */
66 | private function findCritical($chain, $depth)
67 | {
68 | if ($depth <= $this->depth) {
69 | $calls = $chain->value()->extractCalls();
70 | foreach ($calls as $call) {
71 | if ($call->countUse >= 1) {
72 | continue;
73 | }
74 | $call->countUse++;
75 | if ($this->isCallCritical($call)) {
76 | //TODO(dsp25no): not optimal,
77 | // add to tree any critical function node
78 | if($call->name instanceof \PhpParser\Node\Expr\Variable) {
79 | $critical_names = $this->system;
80 | } else {
81 | $critical_names = [$call->name];
82 | }
83 | foreach ($critical_names as $name) {
84 | $this->system_inside->attach($chain->value());
85 | $system = $this->chainTree->getChildByFuncName($name);
86 | $chain->append(new Chain($system->value()), $call);
87 | $this->chainTree->addChain($chain);
88 | $result_chain = $chain->copyChain();
89 | $chain->delLastNode();
90 | yield $result_chain;
91 | }
92 | } else {
93 | $matched_functions = $this->knowledge->getFunctionLikeByCall($call, $this->not_call);
94 | foreach ($matched_functions as $matched) {
95 | // if($chain->value() === $matched) {
96 | // // Don't support recursive chains now
97 | // continue;
98 | // }
99 | if ($this->system_inside->contains($matched)) {
100 | $this->system_inside->attach($chain->value());
101 | foreach ($this->getSuffixChain(
102 | $matched,
103 | $this->depth - $depth) as $suffix
104 | ) {
105 | $chain->append($suffix, $call);
106 | $this->chainTree->addChain($chain);
107 | $result_chain = $chain->copyChain();
108 | $chain->delTail();
109 | yield $result_chain;
110 | }
111 | } elseif (!isset($this->checked[$matched])
112 | or $this->checked[$matched] > $depth
113 | ) {
114 | if (yield from $this->findCritical($chain->append(
115 | new Chain($matched),
116 | $call
117 | ),
118 | $depth + 1)
119 | ) {
120 | $this->system_inside->attach($chain->value());
121 | } else {
122 | $this->checked[$matched] = $depth;
123 | }
124 | $chain->delLastNode();
125 | }
126 | }
127 | }
128 | $call->countUse--;
129 | }
130 | }
131 | }
132 |
133 | public function findAllChains($depth=10)
134 | {
135 | $this->depth = $depth;
136 | $methods = $this->knowledge->getMethods();
137 | $count = sizeof($methods);
138 | $i = 0;
139 | foreach ($methods as $method) {
140 | echo $i++."/".$count."\r";
141 | if(in_array(strval($method->name), $this->magic)) {
142 | foreach ($this->findCritical(new Chain($method), 1) as $new_chain) {
143 | }
144 | }
145 | }
146 | return $this->chainTree;
147 | }
148 |
149 | /**
150 | * Checks if call in system calls list
151 | * @param $call
152 | * @return bool
153 | */
154 | private function isCallCritical($call)
155 | {
156 | return $call instanceof FuncCall and (
157 | in_array($call->name, $this->system) or
158 | $call->name instanceof \PhpParser\Node\Expr\Variable);
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/php-chain/lib/ChainIterator.php:
--------------------------------------------------------------------------------
1 | index = 0;
20 | $this->node = $root;
21 | }
22 |
23 | public function next()
24 | {
25 | $this->node = $this->node->next();
26 | $this->index++;
27 | }
28 |
29 | public function prev()
30 | {
31 | $this->node = $this->node->prev();
32 | $this->index--;
33 | }
34 |
35 | public function rewind()
36 | {
37 | while ($this->node->prev()){
38 | $this->prev();
39 | }
40 | $this->index = 0;
41 | }
42 |
43 | public function current()
44 | {
45 | if($this->valid()){
46 | return $this->node;
47 | }
48 | return null;
49 | }
50 |
51 | public function key()
52 | {
53 | return $this->index;
54 | }
55 |
56 | public function valid()
57 | {
58 | return $this->node instanceof Chain;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/php-chain/lib/ChainTree.php:
--------------------------------------------------------------------------------
1 | function = $function;
25 | $this->children = new \SplObjectStorage();
26 | $this->reverseMapping = new \SplObjectStorage();
27 | $this->depth = 0;
28 | }
29 |
30 | public function setMetric($value) {
31 | $this->metric = $value;
32 | }
33 |
34 | public function hasMetric() {
35 | return $this->metric != 0;
36 | }
37 |
38 | public function getMetric() {
39 | return $this->metric;
40 | }
41 |
42 |
43 | public function getChildren()
44 | {
45 | return $this->children;
46 | }
47 |
48 | public function getChildByCall($call)
49 | {
50 | return $this->children[$call];
51 | }
52 |
53 | public function getChildByFuncName($func_name)
54 | {
55 | foreach ($this->children as $call) {
56 | $node = $this->children[$call];
57 | if($node->function->getFullName() == $func_name) {
58 | return $node;
59 | }
60 | }
61 | }
62 |
63 | public function getParent()
64 | {
65 | return $this->parent;
66 | }
67 |
68 | public function value()
69 | {
70 | return $this->function;
71 | }
72 |
73 | public function getRoot()
74 | {
75 | $item = $this;
76 | while($item->parent) {
77 | $item = $item->parent;
78 | }
79 | return $item;
80 | }
81 |
82 | public function walk()
83 | {
84 | foreach ($this->children as $call) {
85 | yield array($call, $this->children[$call]);
86 | yield from $this->children[$call]->walk();
87 | }
88 | }
89 |
90 | public function reverseWalk()
91 | {
92 | foreach ($this->children as $call) {
93 | yield from $this->children[$call]->reverseWalk();
94 | yield array($call, $this->children[$call]);
95 | }
96 | }
97 |
98 | public function addChildren($function, $call)
99 | {
100 | $node = new ChainTree($function);
101 | $this->children[$call] = $node;
102 | $this->reverseMapping[$node] = $call;
103 | $node->parent = $this;
104 | $node->depth = $this->depth + 1;
105 | return $node;
106 | }
107 |
108 | public function addChain($chain)
109 | {
110 | $root = $this->getRoot();
111 | $reverse_chain = $chain->last();
112 | $system_function = $reverse_chain->value();
113 | $root = $root->getChildByFuncName($system_function->getFullName());
114 | while ($reverse_chain = $reverse_chain->prev()) {
115 | $call = $reverse_chain->getCall();
116 | if(isset($root->children[$call])) {
117 | $root = $root->children[$call];
118 | } else {
119 | $function = $reverse_chain->value();
120 | $root = $root->addChildren($function, $call);
121 | }
122 | }
123 | }
124 |
125 | private function findNode($function, $maxDepth)
126 | {
127 | if($maxDepth > $this->depth) {
128 | foreach ($this->children as $call) {
129 | $node = $this->children[$call];
130 | if ($node->function === $function) {
131 | yield $node;
132 | } else {
133 | yield from $node->findNode($function, $maxDepth);
134 | }
135 | }
136 | }
137 | }
138 |
139 | public function getChainsStartFrom($function, $maxLen)
140 | {
141 | foreach ($this->findNode($function, $maxLen) as $node) {
142 | $chain = new Chain($node->function);
143 | $call = $node->parent->reverseMapping[$node];
144 | $node = $node->parent;
145 | while ($node->parent) {
146 | $chain->append(new Chain($node->function), $call);
147 | $call = $node->parent->reverseMapping[$node];
148 | $node = $node->parent;
149 | }
150 | yield $chain;
151 | }
152 | }
153 |
154 | public function setDfg($dfg)
155 | {
156 | if($this->dfg) {
157 | throw new \Exception("DFG exists!");
158 | }
159 | $this->dfg = $dfg;
160 | }
161 |
162 | public function getDfg()
163 | {
164 | return $this->dfg;
165 | }
166 |
167 | public function jsonSerialize()
168 | {
169 | $json = [
170 | "function" => $this->function->getFullName()
171 | ];
172 | if($this->metric) {
173 | $json["metric"] = $this->metric;
174 | }
175 | $json["children"] = [];
176 |
177 | foreach ($this->children as $call) {
178 | $node = $this->children[$call];
179 | $json["children"][] = $node->jsonSerialize();
180 | }
181 | if($this->metric) {
182 | usort($json["children"],
183 | function ($a, $b) {
184 | return strval($a["metric"]) < strval($b["metric"]);
185 | }
186 | );
187 | }
188 | return $json;
189 | }
190 | }
191 |
--------------------------------------------------------------------------------
/php-chain/lib/ClassLike.php:
--------------------------------------------------------------------------------
1 | name = $name;
25 | $this->node = $node;
26 | $this->knowledge = $knowledge;
27 | $this->methods = null;
28 | }
29 |
30 | public static function create(ParserClassLike $node, $knowledge)
31 | {
32 | $type = __NAMESPACE__.'\\'.explode('_', $node->getType())[1].'_';
33 | $class = $type::create($node, $knowledge);
34 | return $class;
35 | }
36 |
37 | public function addMethod($method)
38 | {
39 | $this->methods[strval($method->name)] = $method;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/php-chain/lib/ClassMethod.php:
--------------------------------------------------------------------------------
1 | class = $class;
22 | $this->flags = $flags;
23 | }
24 |
25 | public static function create(ParserClassMethod $node, $class)
26 | {
27 | return new self($node->name, $node, $node->params, $node->flags, $class);
28 | }
29 |
30 | public function isPrivate()
31 | {
32 | return $this->node->isPrivate();
33 | }
34 |
35 | public function getFullName()
36 | {
37 | return strval($this->class->name) . "->" . strval($this->name);
38 | }
39 |
40 | public function __toString()
41 | {
42 | if($this->_string) {
43 | return $this->_string;
44 | }
45 | $function = parent::__toString();
46 | $this->_string = $this->class->name->toCodeString() . "->" . $function;
47 |
48 | return $this->_string;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/php-chain/lib/Class_.php:
--------------------------------------------------------------------------------
1 | extends = $extends;
23 | $this->implements = $implements;
24 | $this->_inherited_methods = null;
25 | }
26 |
27 | public static function create(ParserClass $node, $knowledge)
28 | {
29 | return new self($node->namespacedName, $node, $knowledge, $node->extends, $node->implements);
30 | }
31 |
32 | public function getExtends()
33 | {
34 | if ($this->extends) {
35 | return $this->knowledge->getClass(strval($this->extends));
36 | }
37 | return null;
38 | }
39 |
40 | public function getImplements()
41 | {
42 | if ($this->implements) {
43 | $res = [];
44 | foreach ($this->implements as $implement) {
45 | if($implement) {
46 | $res[] = $this->knowledge->getClass(strval($implement));
47 | }
48 | }
49 | return $res;
50 | }
51 | return null;
52 | }
53 |
54 | private function getInheritedMethods()
55 | {
56 | if ($this->_inherited_methods !== null) {
57 | return $this->_inherited_methods;
58 | }
59 | $this->_inherited_methods = [];
60 | if($this->extends) {
61 | $extends = $this->knowledge->getClass(strval($this->extends));
62 | if (!$extends) {
63 | echo "Warning! No info about parent of {$this->name} ({$this->extends})".PHP_EOL;
64 | return $this->_inherited_methods;
65 | }
66 | $methods = $extends->getMethods();
67 | if(!$methods) {
68 | return $this->_inherited_methods;
69 | }
70 | $abstract_parent = $extends->node->isAbstract();
71 | foreach ($methods as $name => $method) {
72 | if ($abstract_parent or !$method->isPrivate()) {
73 | $this->_inherited_methods[$name] = clone $method;
74 | $this->_inherited_methods[$name]->class = $this;
75 | }
76 | }
77 | }
78 | return $this->_inherited_methods;
79 | }
80 |
81 | public function getMethods()
82 | {
83 | if($this->methods) {
84 | return array_merge($this->getInheritedMethods(), $this->methods);
85 | }
86 | return $this->getInheritedMethods();
87 | }
88 |
89 | public function isAbstract()
90 | {
91 | return $this->node->isAbstract();
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/php-chain/lib/Dfg.php:
--------------------------------------------------------------------------------
1 | script = $script;
21 | $this->call = $call;
22 | $this->targetVars = new \SplObjectStorage();
23 | $this->targetOps = new \SplObjectStorage();
24 | }
25 |
26 | public function getTargetVar($var, $strict=False)
27 | {
28 | if(!isset($this->targetVars[$var]) and !$strict) {
29 | $this->targetVars[$var] = new TargetVar($var, $this);
30 | }
31 | if(isset($this->targetVars[$var])) {
32 | return $this->targetVars[$var];
33 | }
34 | }
35 |
36 | public function getTargetOp($op, $strict=False)
37 | {
38 | if(!isset($this->targetOps[$op]) and !$strict) {
39 | $this->targetOps[$op] = new TargetOp($op, $this);
40 | }
41 | if(isset($this->targetOps[$op])) {
42 | return $this->targetOps[$op];
43 | }
44 | }
45 |
46 | private function findBlockAndOp()
47 | {
48 | $traverser = new PHPCfg\Traverser;
49 | $finder = new FindBlockAndOP($this->call);
50 | $traverser->addVisitor($finder);
51 | $traverser->traverse($this->script);
52 | return [$finder->block, $finder->op];
53 | }
54 |
55 | private function buildScope($start_op)
56 | {
57 | $important_params = $this->call->getTargetArgs();
58 | if(!$start_op->args) {
59 | // function don't have arguments
60 | return;
61 | }
62 | for ($i = 0; $i < sizeof($start_op->args); $i++) {
63 | if (in_array($i, $important_params)) {
64 | $this->output_params[$i] = $this->getTargetVar($start_op->args[$i]);
65 | }
66 | }
67 | }
68 |
69 | public function countMetric()
70 | {
71 | $metric = 1.0;
72 | foreach ($this->output_params as $var) {
73 | $number = $var->getMetric();
74 | $metric *= $number;
75 | }
76 | return $metric;
77 | }
78 |
79 | public function checkReachability($block)
80 | {
81 | return 1.0;
82 | $checker = new TargetParent($block, $this);
83 | return $checker->isReachable();
84 | }
85 |
86 | public function analyze()
87 | {
88 | list($block, $op) = $this->findBlockAndOp();
89 | $this->buildScope($op);
90 | $reachable = $this->checkReachability($block);
91 | $metric = $this->countMetric();
92 | return [$reachable, $metric];
93 | }
94 |
95 | public function updateMetric()
96 | {
97 | list($block, $op) = $this->findBlockAndOp();
98 | $reachable = $this->checkReachability($block);
99 | $metric = $this->countMetric();
100 | return [$reachable, $metric];
101 | }
102 |
103 | public function getImportantParamNums()
104 | {
105 | $params = $this->script->functions[0]->params;
106 | $nums = [];
107 | for($i = 0; $i < sizeof($params); $i++) {
108 | if (isset($this->targetOps[$params[$i]])) {
109 | $nums[] = $i;
110 | $this->input_params[$i] = $this->targetOps[$params[$i]];
111 | }
112 | }
113 | return $nums;
114 | }
115 |
116 | public function getOutputParams()
117 | {
118 | return $this->output_params;
119 | }
120 |
121 | public function matchOutputInput($prev_output)
122 | {
123 | foreach ($prev_output as $index=>$var) {
124 | $this->input_params[$index]->updateCategory($var->getCategory());
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/php-chain/lib/ExprCall.php:
--------------------------------------------------------------------------------
1 | targetArgs = $targetArgs;
23 | }
24 |
25 | public function getTargetArgs()
26 | {
27 | return $this->targetArgs;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/php-chain/lib/ExprCall/FuncCall.php:
--------------------------------------------------------------------------------
1 | name = $node->name;
19 | $this->argsCount = sizeof($node->args);
20 | $this->node = $node;
21 | $this->countUse = 0;
22 | }
23 |
24 | public function getRegex() {
25 | $regex = "/^";
26 | $regex .= $this->name . "\(";
27 | $regex .= "r{0," . $this->argsCount . "}[^r]*\)";
28 | $regex .= "$/";
29 | return $regex;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/php-chain/lib/ExprCall/MethodCall.php:
--------------------------------------------------------------------------------
1 | name = $node->name;
24 | if ($node->var->name == "this") {
25 | if(!$func) {
26 | throw new \Exception("Method without owner");
27 | }
28 | $this->owner = $func->class->name;
29 | } else {
30 | $this->owner = "*";
31 | }
32 | } elseif ($node instanceof ParserNew) {
33 | $this->name = "__construct";
34 | if($node->class instanceof Node\Identifier or
35 | $node->class instanceof Node\Name) {
36 | $this->owner = $node->class;
37 | } else {
38 | $this->owner = "*";
39 | }
40 | }
41 | $this->argsCount = sizeof($node->args);
42 | $this->node = $node;
43 | $this->countUse = 0;
44 | }
45 |
46 | public function isClassFixed()
47 | {
48 | return $this->owner instanceof Node\Identifier or
49 | $this->owner instanceof Node\Name;
50 | }
51 |
52 | public function isStrict()
53 | {
54 | return $this->name instanceof Node\Identifier or
55 | $this->name instanceof Node\Name;
56 | }
57 |
58 | public function getMethodName()
59 | {
60 | return $this->owner."->".$this->name;
61 | }
62 |
63 | public function getRegex() {
64 | $regex = "/^";
65 | $regex .= $this->owner === "*" ?
66 | "[\w\\\\]+" :
67 | preg_quote($this->owner);
68 | $regex .= "->";
69 | if($this->name instanceof Node\Expr\Variable) {
70 | $regex .= "[\w]+";
71 | } else {
72 | $regex .= $this->name;
73 | }
74 | $regex .= "\(r{0," . $this->argsCount . "}[^r]*\)";
75 | $regex .= "$/";
76 | return $regex;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/php-chain/lib/FindBlockAndOP.php:
--------------------------------------------------------------------------------
1 | call = $call;
18 | }
19 |
20 | public function enterBlock(Block $block, Block $prior = null)
21 | {
22 | }
23 |
24 | public function enterOp(Op $op, Block $block)
25 | {
26 | if($op->getLine() === $this->call->node->getLine() and
27 | ($op->getType() === "Expr_FuncCall" or $op->getType() === "Expr_MethodCall") and
28 | $op->name->value == $this->call->name) {
29 | $this->op = $op;
30 | $this->block = $block;
31 | }
32 | }
33 | public function leaveOp(Op $op, Block $block)
34 | {
35 | }
36 | public function leaveBlock(Block $block, Block $prior = null)
37 | {
38 | if(sizeof($block->children) === 0) {
39 | return \PHPCfg\Visitor::REMOVE_BLOCK;
40 | }
41 | }
42 | public function skipBlock(Block $block, Block $prior = null)
43 | {
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/php-chain/lib/FunctionLike.php:
--------------------------------------------------------------------------------
1 | name = $name;
26 | $this->node = $node;
27 | $this->params = $params;
28 | $this->_calls = null;
29 | }
30 |
31 | public function extractCalls()
32 | {
33 | if ($this->_calls) {
34 | return $this->_calls;
35 | }
36 | $nodeFinder = new NodeFinder;
37 | $calls = $nodeFinder->find($this->node->stmts, function(Node $node) {
38 | return $node instanceof Node\Expr\FuncCall or
39 | $node instanceof Node\Expr\MethodCall or
40 | $node instanceof Node\Expr\New_;
41 | });
42 | $this->_calls = [];
43 | foreach ($calls as $call) {
44 | if ($call instanceof Node\Expr\New_) {
45 | $this->_calls[] = new MethodCall($call);
46 | continue;
47 | }
48 | if (!($call->name instanceof Node\Identifier or
49 | $call->name instanceof Node\Name or
50 | $call->name instanceof Node\Expr\Variable)) {
51 | continue;
52 | }
53 | if ($call instanceof Node\Expr\FuncCall){
54 | $this->_calls[] = new FuncCall($call);
55 | } elseif ($call instanceof Node\Expr\MethodCall) {
56 | $this->_calls[] = new MethodCall($call, $this);
57 | }
58 | }
59 | return $this->_calls;
60 | }
61 |
62 | public function getNumberOfRequiredParameters()
63 | {
64 | for($i = sizeof($this->params) - 1; $i >= 0; $i--) {
65 | $param = $this->params[$i];
66 | if($param->default === null and !$param->variadic) {
67 | return $i + 1;
68 | }
69 | }
70 | return 0;
71 | }
72 |
73 | public function getFullName()
74 | {
75 | return strval($this->name);
76 | }
77 |
78 | public function __toString()
79 | {
80 | if($this->_string) {
81 | return $this->_string;
82 | }
83 | if($this->params) {
84 | $required = $this->getNumberOfRequiredParameters();
85 | $optional = sizeof($this->params) - $required;
86 | $this->_string = $this->name . "(" . str_repeat("r", $required) . \
87 | str_repeat("o", $optional) . \
88 | str_repeat("v", end($this->params)->variadic) . ")";
89 | } else {
90 | $this->_string = $this->name . "()";
91 | }
92 | return $this->_string;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/php-chain/lib/Function_.php:
--------------------------------------------------------------------------------
1 | name, $node, $node->params);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/php-chain/lib/Interface_.php:
--------------------------------------------------------------------------------
1 | extends = $extends;
21 | }
22 |
23 | public static function create(ParserInterface $node, $knowledge)
24 | {
25 | return new self($node->namespacedName, $node, $knowledge, $node->extends);
26 | }
27 |
28 | public function getExtends()
29 | {
30 | if ($this->extends) {
31 | $res = [];
32 | foreach ($this->extends as $extend) {
33 | if($extend) {
34 | $res[] = $this->knowledge->getClass([strval($extend)]);
35 | }
36 | }
37 | return $res;
38 | }
39 | return null;
40 | }
41 |
42 | public function getMethods()
43 | {
44 | // This case was noticed only in code for compatibility with old requirements
45 | echo "Get methods from interface ({$this->name})";
46 | return [];
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/php-chain/lib/Parser.php:
--------------------------------------------------------------------------------
1 | target = $target;
21 | $this->knowledge = new ProjectKnowledge();
22 | $this->parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
23 | $this->traverser = new NodeTraverser;
24 | $this->visitors[] = new NameResolver;
25 | $this->visitors[] = new Collector($this->knowledge);
26 | if($config["features"]["foreach"]) {
27 | $this->visitors[] = new LoopResolver();
28 | }
29 | if($config["features"]["offsetGet"]) {
30 | $this->visitors[] = new ArrayAccessResolver();
31 | }
32 | foreach ($this->visitors as $visitor) {
33 | $this->traverser->addVisitor($visitor);
34 | }
35 | }
36 |
37 | public function collectMethods() {
38 | foreach ($this->knowledge->getClasses() as $class) {
39 | if(!$class instanceof Class_ or $class->isAbstract()) {
40 | continue; // Don't load interfaces' methods, because they don't have body
41 | // and abstract classes' methods^ because we can't create object of abstract class
42 | }
43 | $methods = $class->getMethods();
44 | foreach ($methods as $method) {
45 | $this->knowledge->addMethod($method);
46 | }
47 | }
48 | }
49 |
50 | public function parse()
51 | {
52 | $files = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->target));
53 | $files = new \RegexIterator(
54 | $files,
55 | // ignore directories with tests
56 | '/^(?!.*(?=[\/\\\\][Tt]ests?[\/\\\\])).*\.(php|inc)$/'
57 | );
58 | foreach ($files as $file) {
59 | $filename = $file->getPathName();
60 |
61 | $code = file_get_contents($filename);
62 | try {
63 | // parse
64 | $stmts = $this->parser->parse($code);
65 |
66 | $this->traverser->traverse($stmts);
67 |
68 | } catch (\PhpParser\Error $e) {
69 | echo "Fail to parse $filename: ", $e->getMessage().PHP_EOL;
70 | }
71 | }
72 | $this->collectMethods();
73 | return $this->knowledge;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/php-chain/lib/ProjectKnowledge.php:
--------------------------------------------------------------------------------
1 | classes = [];
22 | $this->functions = [];
23 | $this->methods = [];
24 | }
25 |
26 | public function &getClasses()
27 | {
28 | return $this->classes;
29 | }
30 |
31 | public function &getFunctions()
32 | {
33 | return $this->functions;
34 | }
35 |
36 | public function &getMethods()
37 | {
38 | return $this->methods;
39 | }
40 |
41 | public function getClass(string $name) {
42 | return $this->classes[$name];
43 | }
44 |
45 | public function getFunction(string $name) {
46 | return $this->functions[$name];
47 | }
48 |
49 | public function getMethod(string $name) {
50 | return $this->methods[$name];
51 | }
52 |
53 | public function addClass($class) {
54 | $this->classes[strval($class->name)] = $class;
55 | }
56 |
57 | public function addFunction($function) {
58 | $this->functions[$function->getFullName()] = $function;
59 | }
60 |
61 | public function addMethod($method) {
62 | $fullName = $method->getFullName();
63 | $this->methods[$fullName] = $method;
64 | if($method->name == "__call") {
65 | $this->__call_methods[] = $method;
66 | }
67 | }
68 |
69 | public function getFunctionLikeByCall($call, $strict=true)
70 | {
71 | if ($call instanceof FuncCall) {
72 | $regex = $call->getRegex();
73 | $searchSet = $this->functions;
74 | return new \RegexIterator(new \ArrayIterator($searchSet), $regex);
75 | }
76 | $classFixed = $call->isClassFixed();
77 | if ($classFixed) {
78 | if ($call->isStrict()) {
79 | $methodName = $call->getMethodName();
80 | $method = $this->getMethod($methodName);
81 | if ($method) {
82 | return [$method];
83 | } else {
84 | return [];
85 | }
86 | } else {
87 | $class = $this->getClass($call->owner);
88 | if (!$class) {
89 | return [];
90 | }
91 | $searchSet = $class->getMethods();
92 | }
93 | } else {
94 | $searchSet = $this->methods;
95 | }
96 | $regex = $call->getRegex();
97 | $iterator = new \AppendIterator();
98 | $iterator->append(new \RegexIterator(new \ArrayIterator($searchSet), $regex));
99 | if(!$classFixed && !$strict) {
100 | $iterator->append(new \ArrayIterator($this->__call_methods));
101 | } elseif(!$strict) {
102 | $iterator->append(new \ArrayIterator(["__call" => $searchSet["__call"]]));
103 | }
104 | return $iterator;
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/php-chain/lib/TargetOp.php:
--------------------------------------------------------------------------------
1 | op = $op;
20 | $this->dfg = $dfg;
21 | $this->metric = 0.0;
22 | }
23 |
24 | public function composeBinaryOp()
25 | {
26 | $right = $this->dfg->getTargetVar($this->op->right);
27 | $left = $this->dfg->getTargetVar($this->op->left);
28 | $right_category = $right->getCategory();
29 | $left_category = $left->getCategory();
30 | $this->category = null;
31 | if (!$right_category) {
32 | $this->category = $left_category;
33 | } elseif ($right_category == $left_category) {
34 | $this->category = $right_category;
35 | } elseif ($right_category == "VAR" or $left_category == "VAR") {
36 | $this->category = "VAR";
37 | } elseif ($right_category == "CONST") {
38 | $this->category = $left_category;
39 | } elseif ($left_category == "CONST") {
40 | $this->category = $right_category;
41 | } elseif ($right_category == "FUNC_PARAM" or $left_category == "FUNC_PARAM") {
42 | $this->category = "FUNC_PARAM";
43 | } else {
44 | $this->category = "TMP";
45 | }
46 | $this->metric = TargetVar::getNumber($this->category);
47 | }
48 |
49 | public function composePhiOp()
50 | {
51 | $this->category = "TMP";
52 | foreach ($this->op->vars as $var) {
53 | $target_var = $this->dfg->getTargetVar($var);
54 | $category = $target_var->getCategory();
55 | if($category == "TMP") {
56 | continue;
57 | }
58 | $metric = TargetVar::getNumber($category);
59 | if($metric > $this->metric) {
60 | $this->metric = $metric;
61 | $this->category = $category;
62 | }
63 | }
64 | if($this->category === "TMP") {
65 | $this->metric = TargetVar::getNumber($this->category);
66 | }
67 | }
68 |
69 | public function classify() {
70 | $type = $this->op->getType();
71 | if ($type == "Expr_PropertyFetch") {
72 | $in = $this->dfg->getTargetVar($this->op->var);
73 | $this->category = $in->getCategory();
74 | $this->result = $in->value;
75 | }
76 | elseif ($type == "Expr_Param") {
77 | $this->category = "FUNC_PARAM";
78 | }
79 | elseif ($type == "Expr_Assign") {
80 | $assign_val = $this->dfg->getTargetVar($this->op->expr);
81 | $this->category = $assign_val->getCategory();
82 | $this->result = $assign_val->value;
83 | }
84 | elseif ($type == "Phi") {
85 | $this->composePhiOp();
86 | }
87 | elseif (substr($type, 0, 13) == "Expr_BinaryOp") {
88 | $this->composeBinaryOp();
89 | if ($this->category == "CONST") {
90 | $this->result = TargetOp::countExpr($this->dfg->getTargetVar($this->op->result), $this->dfg);
91 | }
92 | }
93 | else {
94 | $this->category = "VAR";
95 | }
96 | }
97 |
98 | public static function countExpr(TargetVar $expr, $dfg) {
99 | $var_type = $expr->var->getType();
100 | if ($var_type == "Literal") {
101 | return $expr->var->value;
102 | }
103 | foreach ($expr->var->ops as $op) {
104 | $type = $op->getType();
105 | if(substr($type, 0, 13) == "Expr_BinaryOp") {
106 | if ($type == "Expr_BinaryOp_Plus") {
107 | return TargetOp::countExpr($dfg->getTargetVar($op->left, $dfg), $dfg) +
108 | TargetOp::countExpr($dfg->getTargetVar($op->left, $dfg), $dfg);
109 | } elseif ($type == "Expr_BinaryOp_Minus") {
110 | return TargetOp::countExpr($dfg->getTargetVar($op->left, $dfg), $dfg) -
111 | TargetOp::countExpr($dfg->getTargetVar($op->left, $dfg), $dfg);
112 | } elseif ($type == "Expr_BinaryOp_Greater") {
113 | return TargetOp::countExpr($dfg->getTargetVar($op->left, $dfg), $dfg) >
114 | TargetOp::countExpr($dfg->getTargetVar($op->left, $dfg), $dfg);
115 | } elseif ($type == "Expr_BinaryOp_Smaller") {
116 | return TargetOp::countExpr($dfg->getTargetVar($op->left, $dfg), $dfg) <
117 | TargetOp::countExpr($dfg->getTargetVar($op->left, $dfg), $dfg);
118 | } elseif ($type == "Expr_BinaryOp_Concat") {
119 | return TargetOp::countExpr($dfg->getTargetVar($op->left, $dfg), $dfg) .
120 | TargetOp::countExpr($dfg->getTargetVar($op->left, $dfg), $dfg);
121 | }
122 | }
123 | }
124 | return $expr->value;
125 | }
126 |
127 | public function updateCategory($category=null)
128 | {
129 | $type = $this->op->getType();
130 | if ($type == "Expr_Param") {
131 | if ($category === null) {
132 | throw new \Exception("Bad update category");
133 | }
134 | $this->category = $category;
135 | } elseif ($type == "Phi") {
136 | $this->composePhiOp();
137 | } elseif (substr($type, 0, 13) == "Expr_BinaryOp") {
138 | $this->composeBinaryOp();
139 | if ($this->category == "CONST") {
140 | $this->result = TargetOp::countExpr($this->dfg->getTargetVar($this->op->result), $this->dfg);
141 | }
142 | }
143 | $var = $this->op->result;
144 | $var = $this->dfg->getTargetVar($var);
145 | $var->updateCategory($this->category);
146 | }
147 |
148 | public function getCategory()
149 | {
150 | if (!$this->category) {
151 | $this->classify();
152 | }
153 | return $this->category;
154 | }
155 |
156 | public function getMetric()
157 | {
158 | if ($this->category == null) {
159 | $this->classify();
160 | }
161 | return $this->metric;
162 | }
163 |
164 | }
165 |
--------------------------------------------------------------------------------
/php-chain/lib/TargetParent.php:
--------------------------------------------------------------------------------
1 | block = $block;
19 | $this->dfg = $dfg;
20 | if (!$conditions) {
21 | $this->conditions = [];
22 | }
23 | }
24 |
25 | public function functionStartBlock() {
26 | return sizeof($this->block->parents) == 0;
27 | }
28 |
29 | public function getCondition() {
30 | return $this->block->children[sizeof($this->block->children) - 1];
31 | }
32 |
33 | public function checkCondition($cond) {
34 | if ($cond->getType() == "Stmt_Jump") {
35 | $reachable = 1;
36 | } elseif ($cond->getType() == "Stmt_JumpIf") {
37 | //ToDo: check if it has already been checked for another branch (if/else)
38 | // if ($this->ifConditionVisited($cond)) {
39 | // $reachable = 1;
40 | // return $reachable;
41 | // }
42 | $var_cond = $this->dfg->getTargetVar($cond->cond);
43 | // ToDo: check value and condition op->getType() + check if or else block == this->block
44 | // foreach ($cond->cond->ops as $op) {
45 | // var_dump($op->getType());
46 | // }
47 | $cond_category = $var_cond->getCategory();
48 | if ($cond_category == "PROPERTY") {
49 | $reachable = 1 ;
50 | } elseif ($cond_category == "CONST") {
51 | $reachable = 0.8;
52 | } else {
53 | $reachable = 0.1;
54 | }
55 | } else {
56 | // ToDO: implement other cases
57 | $reachable = 0.1;
58 | }
59 | return $reachable;
60 | }
61 |
62 | public function isReachable() {
63 | if ($this->functionStartBlock()) {
64 | $this->reachable = 1;
65 | return $this->reachable;
66 | }
67 | $max = 0;
68 | foreach ($this->block->parents as $block_parent) {
69 | $parent = new TargetParent($block_parent, $this->dfg,$this->conditions);
70 | $cond = $parent->getCondition();
71 | $reachable = $this->checkCondition($cond);
72 | $this->conditions = clone $cond;
73 | $reachable *= $parent->isReachable();
74 | if ($reachable == 1) {
75 | $this->reachable = 1;
76 | return $reachable;
77 | } elseif ($reachable > $max) {
78 | $max = $reachable;
79 | }
80 | }
81 | return $max;
82 | }
83 |
84 | public function ifConditionVisited($cond) {
85 | foreach ($this->conditions as $condition) {
86 | // if ($condition == $cond) {
87 | // var_dump("[");
88 | if ($cond->if == $condition->else or $cond->else == $condition->if) {
89 | // return true;
90 | }
91 | }
92 | return false;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/php-chain/lib/TargetVar.php:
--------------------------------------------------------------------------------
1 | var = $operand;
18 | $this->dfg = $dfg;
19 | $this->metric = 1.0;
20 | $this->processing = False;
21 | }
22 |
23 | private function classify()
24 | {
25 | $type = $this->var->getType();
26 | if ($type === "Literal" or $type === "NullOperand") {
27 | $this->category = "CONST";
28 | if ($type == "Literal") {
29 | $this->value = $this->var->value;
30 | }
31 | } elseif ($type === "Variable") {
32 | $this->category = "VAR";
33 | } elseif ($type === "Temporary") {
34 | $op = $this->var->ops[0];
35 | $target_op = $this->dfg->getTargetOp($op);
36 | $this->category = $target_op->getCategory();
37 | } elseif ($type === "BoundVariable") {
38 | $this->category = "PROPERTY";
39 | }
40 | }
41 |
42 | public function getMetric()
43 | {
44 | $category = $this->getCategory();
45 | $this->metric = TargetVar::getNumber($category);
46 | return $this->metric;
47 | }
48 |
49 | public function getCategory()
50 | {
51 | if (!$this->category && !$this->processing) {
52 | $this->processing = True; //TODO: move to classify
53 | $this->classify();
54 | $this->processing = False;
55 | } elseif ($this->processing) {
56 | return "TMP";
57 | }
58 | return $this->category;
59 | }
60 |
61 | public function updateCategory($category)
62 | {
63 | if($this->processing)
64 | return;
65 | $this->processing = true;
66 | $this->category = $category;
67 | $this->metric = TargetVar::getNumber($this->category);
68 | foreach ($this->var->usages as $op) {
69 | $target_op = $this->dfg->getTargetOp($op, True);
70 | if($target_op) {
71 | $target_op->updateCategory();
72 | }
73 | }
74 | $this->processing = false;
75 | }
76 |
77 | public static function getNumber($category)
78 | {
79 | if ($category == "CONST") {
80 | return 0.5;
81 | } elseif ($category == "PROPERTY") {
82 | return 0.9;
83 | } elseif ($category == "VAR") {
84 | return 0.2;
85 | } elseif ($category == "FUNC_PARAM") {
86 | return 0.1;
87 | } elseif ($category == "TMP") {
88 | return 1;
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/php-chain/lib/Trait_.php:
--------------------------------------------------------------------------------
1 | namespacedName, $node, $knowledge);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/php-chain/lib/TreeAnalyzer.php:
--------------------------------------------------------------------------------
1 | chainTree = $chainTree;
19 | $criticalNodes = $this->chainTree->getChildren();
20 | foreach ($criticalNodes as $key) {
21 | foreach ($criticalNodes[$key]->getChildren() as $call) {
22 | $call->setTargetArgs(
23 | range(0, sizeof($criticalNodes[$key]->value()->params) - 1)
24 | );
25 | }
26 | }
27 | }
28 |
29 | private function intraProceduralAnalyze()
30 | {
31 | $parser = new PHPCfg\Parser(
32 | (new ParserFactory)->create(ParserFactory::PREFER_PHP7)
33 | );
34 | $factory = new AstBuilder();
35 |
36 | foreach ($this->chainTree->walk() as list($call, $chain_node)) {
37 | if ($call instanceof \StdClass) {
38 | continue;
39 | }
40 | $function = $chain_node->value();
41 | $ast = $function->node;
42 | if ($function instanceof ClassMethod) {
43 | $new_ast = $factory->class($function->class->name);
44 | $new_ast->addStmt($ast);
45 | $ast = $new_ast->getNode();
46 | }
47 |
48 | $script = $parser->parseAst(array($ast), "stub_name");
49 | $dfg = new Dfg($script, $call);
50 |
51 | $metric = $dfg->analyze();
52 |
53 | $chain_node->setMetric($metric);
54 | $chain_node->setDfg($dfg);
55 | $important_params = $dfg->getImportantParamNums();
56 | foreach ($chain_node->getChildren() as $call) {
57 | $call->setTargetArgs($important_params);
58 | }
59 | }
60 | }
61 |
62 | public function interProceduralAnalyze()
63 | {
64 | foreach ($this->chainTree->reverseWalk() as list($chain_call, $chain_node)) {
65 | $children = $chain_node->getChildren();
66 | if ($children->count() === 0) {
67 | continue;
68 | }
69 |
70 | $best_child = null;
71 | $best_metric = [-1, -1];
72 | foreach ($children as $call) {
73 | $child = $children[$call];
74 | $metric = $child->getMetric();
75 | if ($metric[0] > $best_metric[0] or
76 | $metric[0] === $best_metric[0] and
77 | $metric[1] > $best_metric[1]) {
78 | $best_metric = $metric;
79 | $best_child = $child;
80 | }
81 | }
82 |
83 | $params = $best_child->getDfg()->getOutputParams();
84 | if(!$chain_call instanceof \StdClass) {
85 | $dfg = $chain_node->getDfg();
86 | $dfg->matchOutputInput($params);
87 | $metric = $dfg->updateMetric();
88 | $chain_node->setMetric($metric);
89 | } else {
90 | $chain_node->setMetric($best_child->getMetric());
91 | }
92 | }
93 | }
94 |
95 | public function analyze()
96 | {
97 | echo "IntraProcedural Analyze".PHP_EOL;
98 | $this->intraProceduralAnalyze();
99 | echo "InterProcedural Analyze".PHP_EOL;
100 | $this->interProceduralAnalyze();
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/php-chain/lib/TreePrinter.php:
--------------------------------------------------------------------------------
1 | walk() as list($call, $chain_node)) {
19 | if($chain_node->getParent()->getParent()) {
20 | $graph .= '"'.$chain_node->getParent()->value().'" -> "' . $chain_node->value() . '";' . PHP_EOL;
21 | }
22 | }
23 | $graph .= "}".PHP_EOL;
24 | return $graph;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/php-chain/load_chains.php:
--------------------------------------------------------------------------------
1 | parse();
9 |
10 | echo "Parsed".PHP_EOL;
11 |
--------------------------------------------------------------------------------
/php-chain/test-project/classes.php:
--------------------------------------------------------------------------------
1 | base_priv();
30 | }
31 |
32 | private function base_priv(){
33 | $a = 1;
34 | $b = 2;
35 | $c = 3;
36 | func_splat($a,$b,$c,8,'qwe');
37 | }
38 | }
39 |
40 | //class with only new methods
41 | class A extends Base
42 | {
43 | public function __wakeup(){
44 | func_arr([]);
45 | func_splat(1,2,3,4);
46 | $this->a_pub('whoami',[1,2,3,]);
47 | }
48 | public function a_pub($p1,$p2){
49 | func_str_sys($p1);
50 | func_files('test.txt',2,'None');
51 | func_splat($p2);
52 | }
53 |
54 | protected function a_prt($p1,...$ps){
55 | print($p1.' a_prt');
56 | func_splat($ps);
57 | $this->a_priv();
58 | }
59 |
60 | private function a_priv($p=1,$p2=2,$p3=3){
61 | var_dump($p,$p2,$p3);
62 | }
63 | }
64 |
65 | //class with redeclaration of parents' methods
66 | class B extends Base
67 | {
68 | public function b_pub($a){
69 | $this->base_pub($a);
70 | }
71 |
72 | protected function b_prt(){
73 | print('b2');
74 | }
75 |
76 | private function b_priv(){
77 | print('b3');
78 | }
79 |
80 | public function base_pub($a)
81 | {
82 | $this->b_priv();
83 | $this->b_prt();
84 | }
85 | public function base_prt()
86 | {
87 | func_str_sys('whoami');
88 | }
89 | }
90 |
91 |
92 | //the grandson class of Base
93 | class C extends B{
94 | private $magic = "";
95 | public function __call($name, $arguments){
96 | var_dump($name);
97 | var_dump($arguments);
98 | system($this->magic);
99 | func_str_sys($this->magic);
100 |
101 | }
102 | public function __wakeup()
103 | {
104 | parent::base_prt();
105 | $this->base_prt();
106 | }
107 |
108 | public function c_pub(){
109 | $this->base_pub('pwd');
110 | $this->base_prt();
111 | func_str_sys('whoami');
112 | }
113 |
114 | protected function c_prt(){
115 | print('c2');
116 | $this->c_priv();
117 | }
118 |
119 | private function c_priv(){
120 | print('c3');
121 | }
122 |
123 | //is it possible to redeclare grandfathers's method?
124 | public function base_pub2(...$splat){
125 | echo 'grandson';
126 | }
127 | }
--------------------------------------------------------------------------------
/php-chain/test-project/functions.php:
--------------------------------------------------------------------------------
1 | $val){
5 | echo $key.'='.$val;
6 | }
7 | }
8 |
9 | function func_files($file_name, $file_size, $comment, $file_write=null) : string {
10 | $f = fopen($file_name,'r');
11 | $res = fread($f,$file_size);
12 | fclose($f);
13 | if ($file_write != null) {
14 | $f = fopen($file_write,'w');
15 | fwrite($f,$comment);
16 | fclose($f);
17 | }
18 | else {
19 | echo $comment;
20 | }
21 | return $res;
22 | }
23 |
24 | function func_splat($a, $b = null, ...$c){
25 | var_dump($a, $b, $c);
26 | }
27 |
28 | function func_str_sys($str){
29 | system($str);
30 | }
31 |
32 | ?>
--------------------------------------------------------------------------------
/php-chain/test-project/interfaces.php:
--------------------------------------------------------------------------------
1 | &2
29 | exit 1
30 | ;;
31 | esac
32 | done
33 | }
34 |
35 | function check_image() {
36 | if ! docker inspect --type=image php-chain &> /dev/null
37 | then
38 | echo "Building container"
39 | make
40 | fi
41 | }
42 |
43 | function usage() {
44 | cat <&2
64 | exit 1
65 | fi
66 |
67 | shift
68 |
69 | if [[ " ${white_list[*]} " != *" $operation "* ]]; then
70 | echo "Unknown operation '$operation'" >&2
71 | usage
72 | exit 1
73 | fi
74 |
75 | EXTRA_DOCKER_OPTIONS=()
76 | COMMAND=()
77 |
78 | case "$operation" in
79 | analyze )
80 | parse_args "$@"
81 | ;;
82 | build_chains )
83 | parse_args "$@"
84 | EXTRA_DOCKER_OPTIONS+=("--entrypoint" "php")
85 | COMMAND+=("$operation.php")
86 | ;;
87 | count_metrics )
88 | parse_chains_args "$@"
89 | EXTRA_DOCKER_OPTIONS+=("--entrypoint" "php")
90 | COMMAND+=("$operation.php")
91 | ;;
92 | help )
93 | usage
94 | exit 0
95 | ;;
96 | esac
97 |
98 | check_image
99 |
100 | echo "Starting analyze"
101 | exec docker run --rm \
102 | "${EXTRA_DOCKER_OPTIONS[@]}" \
103 | -v "$(pwd)/res":/res \
104 | php-chain "${COMMAND[@]}"
105 |
--------------------------------------------------------------------------------