pax_global_header00006660000000000000000000000064122267560760014527gustar00rootroot0000000000000052 comment=a1dfd3d3e232c6cd0124b7aaad74920a78950c29 japi-compliance-checker-1.3.5/000077500000000000000000000000001222675607600161725ustar00rootroot00000000000000japi-compliance-checker-1.3.5/INSTALL000066400000000000000000000033551222675607600172310ustar00rootroot00000000000000 Copyright (C) 2011 Institute for System Programming, RAS Copyright (C) 2011 Russian Linux Verification Center Copyright (C) 2011-2013 ROSA Laboratory All rights reserved. RELEASE INFORMATION Project: Java API Compliance Checker (Java ACC) Version: 1.3.5 Date: 2013-10-14 This file explains how to install and setup environment for the tool in your computer. Content: 1. Requirements for Linux, FreeBSD and Mac OS X 2. Requirements for MS Windows 3. Configuring and Installing 4. Running the Tool 5. Advanced Usage 1. REQUIREMENTS FOR LINUX, FREEBSD and MAC OS X =============================================== 1. JDK (javap, javac) 2. Perl 5 (5.8 or newer) 2. REQUIREMENTS FOR MS WINDOWS ============================== 1. JDK (javap, javac) 2. Active Perl 5 (5.8 or newer) 3. CONFIGURING AND INSTALLING ============================= This command will install an japi-compliance-checker program in the PREFIX/bin system directory: sudo perl Makefile.pl -install --prefix=PREFIX [/usr, /usr/local, ...] 3.1 Update sudo perl Makefile.pl -update --prefix=PREFIX 3.2 Remove sudo perl Makefile.pl -remove --prefix=PREFIX 4. RUNNING THE TOOL =================== 1. japi-compliance-checker OLD.jar NEW.jar 5. ADVANCED USAGE =================== 1. Create XML-descriptors for two versions of a library (OLD.xml and NEW.xml): 1.0 /path1/to/JAR(s)/ /path2/to/JAR(s)/ ... 2. japi-compliance-checker -lib NAME -old OLD.xml -new NEW.xml 3. For more advanced usage, see doc/Readme.html Enjoy! japi-compliance-checker-1.3.5/LICENSE000066400000000000000000001252431222675607600172060ustar00rootroot00000000000000This program is free software. You may use, redistribute and/or modify it under the terms of either the GNU General Public License (GPL) or the GNU Lesser General Public License (LGPL). --------------------------------------------------------------------------- GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. --------------------------------------------------------------------------- GNU LIBRARY GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1991 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the library GPL. It is numbered 2 because it goes with version 2 of the ordinary GPL.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Library General Public License, applies to some specially designated Free Software Foundation software, and to any other libraries whose authors decide to use it. You can use it for your libraries, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library, or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link a program with the library, you must provide complete object files to the recipients so that they can relink them with the library, after making changes to the library and recompiling it. And you must show them these terms so they know their rights. Our method of protecting your rights has two steps: (1) copyright the library, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the library. Also, for each distributor's protection, we want to make certain that everyone understands that there is no warranty for this free library. If the library is modified by someone else and passed on, we want its recipients to know that what they have is not the original version, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that companies distributing free software will individually obtain patent licenses, thus in effect transforming the program into proprietary software. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License, which was designed for utility programs. This license, the GNU Library General Public License, applies to certain designated libraries. This license is quite different from the ordinary one; be sure to read it in full, and don't assume that anything in it is the same as in the ordinary license. The reason we have a separate public license for some libraries is that they blur the distinction we usually make between modifying or adding to a program and simply using it. Linking a program with a library, without changing the library, is in some sense simply using the library, and is analogous to running a utility program or application program. However, in a textual and legal sense, the linked executable is a combined work, a derivative of the original library, and the ordinary General Public License treats it as such. Because of this blurred distinction, using the ordinary General Public License for libraries did not effectively promote software sharing, because most developers did not use the libraries. We concluded that weaker conditions might promote sharing better. However, unrestricted linking of non-free programs would deprive the users of those programs of all benefit from the free status of the libraries themselves. This Library General Public License is intended to permit developers of non-free programs to use free libraries, while preserving your freedom as a user of such programs to change the free libraries that are incorporated in them. (We have not seen how to achieve this as regards changes in header files, but we have achieved it as regards changes in the actual functions of the Library.) The hope is that this will lead to faster development of free libraries. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, while the latter only works together with the library. Note that it is possible for a library to be covered by the ordinary General Public License rather than by this special one. GNU LIBRARY GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Library General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also compile or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. c) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. d) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Library General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! japi-compliance-checker-1.3.5/Makefile.pl000077500000000000000000000164231222675607600202550ustar00rootroot00000000000000#!/usr/bin/perl ########################################################################### # Makefile for Java API Compliance Checker # Install/remove the tool for GNU/Linux, FreeBSD and Mac OS X # # Copyright (C) 2011 Russian Linux Verification Center # Copyright (C) 2011-2013 ROSA Lab # # Written by Andrey Ponomarenko # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License or the GNU Lesser # General Public License as published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # and the GNU Lesser General Public License along with this program. # If not, see . ########################################################################### use Getopt::Long; Getopt::Long::Configure ("posix_default", "no_ignore_case"); use File::Path qw(mkpath rmtree); use File::Spec qw(catfile file_name_is_absolute); use File::Copy qw(copy); use File::Basename qw(dirname); use Cwd qw(abs_path); use File::Find; use Config; use strict; my $TOOL_SNAME = "japi-compliance-checker"; my $ARCHIVE_DIR = abs_path(dirname($0)); my $HELP_MSG = " NAME: Makefile for Java API Compliance Checker DESCRIPTION: Install $TOOL_SNAME command and private modules. USAGE: sudo perl $0 -install -prefix /usr sudo perl $0 -update -prefix /usr sudo perl $0 -remove -prefix /usr OPTIONS: -h|-help Print this help. --prefix=PREFIX Install files in PREFIX [/usr/local]. -install Command to install the tool. -update Command to update existing installation. -remove Command to remove the tool. EXTRA OPTIONS: --destdir=DESTDIR This option is for maintainers to build RPM or DEB packages inside the build root. The environment variable DESTDIR is also supported. \n"; if(not @ARGV) { print $HELP_MSG; exit(0); } my ($PREFIX, $DESTDIR, $Help, $Install, $Update, $Remove); GetOptions( "h|help!" => \$Help, "prefix=s" => \$PREFIX, "destdir=s" => \$DESTDIR, "install!" => \$Install, "update!" => \$Update, "remove!" => \$Remove ) or exit(1); sub scenario() { if($Help) { print $HELP_MSG; exit(0); } if(not $Install and not $Update and not $Remove) { print STDERR "ERROR: command is not selected (-install, -update or -remove)\n"; exit(1); } if($PREFIX ne "/") { $PREFIX=~s/[\/]+\Z//g; } if(not $PREFIX) { # default prefix if($Config{"osname"}!~/win/i) { $PREFIX = "/usr/local"; } } if(my $Var = $ENV{"DESTDIR"}) { print "Using DESTDIR environment variable\n"; $DESTDIR = $Var; } if($DESTDIR) { if($DESTDIR ne "/") { $DESTDIR=~s/[\/]+\Z//g; } if(not isAbs($DESTDIR)) { print STDERR "ERROR: destdir is not absolute path\n"; exit(1); } if(not -d $DESTDIR) { print STDERR "ERROR: you should create destdir directory first\n"; exit(1); } $PREFIX = $DESTDIR.$PREFIX; if(not -d $PREFIX) { print STDERR "ERROR: you should create installation directory first (destdir + prefix):\n mkdir -p $PREFIX\n"; exit(1); } } else { if(not isAbs($PREFIX)) { print STDERR "ERROR: prefix is not absolute path\n"; exit(1); } if(not -d $PREFIX) { print STDERR "ERROR: you should create prefix directory first\n"; exit(1); } } print "INSTALL PREFIX: $PREFIX\n"; # paths my $EXE_PATH = catFile($PREFIX, "bin"); my $MODULES_PATH = catFile($PREFIX, "share", $TOOL_SNAME); my $REL_PATH = catFile("..", "share", $TOOL_SNAME); my $TOOL_PATH = catFile($EXE_PATH, $TOOL_SNAME); if(not -w $PREFIX) { print STDERR "ERROR: you should be root\n"; exit(1); } if($Remove or $Update) { if(-e $EXE_PATH."/".$TOOL_SNAME) { # remove executable print "-- Removing $TOOL_PATH\n"; unlink($EXE_PATH."/".$TOOL_SNAME); } if(-d $MODULES_PATH) { # remove modules print "-- Removing $MODULES_PATH\n"; rmtree($MODULES_PATH); } } if($Install or $Update) { if(-e $EXE_PATH."/".$TOOL_SNAME or -e $MODULES_PATH) { # check installed if(not $Remove) { print STDERR "ERROR: you should remove old version first (`perl $0 -remove --prefix=$PREFIX`)\n"; exit(1); } } # configure my $Content = readFile($ARCHIVE_DIR."/".$TOOL_SNAME.".pl"); if($DESTDIR) { # relative path $Content=~s/MODULES_INSTALL_PATH/$REL_PATH/; } else { # absolute path $Content=~s/MODULES_INSTALL_PATH/$MODULES_PATH/; } # copy executable print "-- Installing $TOOL_PATH\n"; mkpath($EXE_PATH); writeFile($EXE_PATH."/".$TOOL_SNAME, $Content); chmod(0775, $EXE_PATH."/".$TOOL_SNAME); if($Config{"osname"}=~/win/i) { writeFile($EXE_PATH."/".$TOOL_SNAME.".cmd", "\@perl \"$TOOL_PATH\" \%*"); } # copy modules if(-d $ARCHIVE_DIR."/modules") { print "-- Installing $MODULES_PATH\n"; mkpath($MODULES_PATH); copyDir($ARCHIVE_DIR."/modules", $MODULES_PATH); } # check PATH my $Warn = "WARNING: your PATH variable doesn't include \'$EXE_PATH\'\n"; if($Config{"osname"}=~/win/i) { if($ENV{"PATH"}!~/(\A|[:;])\Q$EXE_PATH\E[\/\\]?(\Z|[:;])/i) { print $Warn; } } else { if($ENV{"PATH"}!~/(\A|[:;])\Q$EXE_PATH\E[\/\\]?(\Z|[:;])/) { print $Warn; } } } exit(0); } sub catFile(@) { return File::Spec->catfile(@_); } sub isAbs($) { return File::Spec->file_name_is_absolute($_[0]); } sub copyDir($$) { my ($From, $To) = @_; my %Files; find(\&wanted, $From); sub wanted { $Files{$File::Find::dir."/$_"} = 1 if($_ ne "."); } foreach my $Path (sort keys(%Files)) { my $Inst = $Path; $Inst=~s/\A\Q$ARCHIVE_DIR\E/$To/; if(-d $Path) { # directories mkpath($Inst); } else { # files mkpath(dirname($Inst)); copy($Path, $Inst); } } } sub readFile($) { my $Path = $_[0]; return "" if(not $Path or not -f $Path); open(FILE, $Path) || die ("can't open file \'$Path\': $!\n"); local $/ = undef; my $Content = ; close(FILE); return $Content; } sub writeFile($$) { my ($Path, $Content) = @_; return if(not $Path); open(FILE, ">".$Path) || die ("can't open file \'$Path\': $!\n"); print FILE $Content; close(FILE); } scenario();japi-compliance-checker-1.3.5/README000066400000000000000000000005101222675607600170460ustar00rootroot00000000000000NAME: Java API Compliance Checker (Java ACC) - a tool for checking backward binary and source-level compatibility of a Java library API. INSTALL: sudo perl Makefile.pl -install --prefix=/usr USAGE: japi-compliance-checker OLD.jar NEW.jar ADV. USAGE: For advanced usage, see doc/Readme.html or output of --help option.japi-compliance-checker-1.3.5/doc/000077500000000000000000000000001222675607600167375ustar00rootroot00000000000000japi-compliance-checker-1.3.5/doc/Changes.html000066400000000000000000000243471222675607600212070ustar00rootroot00000000000000 Java API Compliance Checker - Change Log

Java API Compliance Checker - Change Log

Contents

Version 1.3.5 (October 14, 2013)

japi-compliance-checker-1.3.5.tar.gz
This is a bug-fix release.

  • Improvements
    • Support for Java 7
  • Bug Fixes
    • Corrected formatting of output html reports
    • Fixed issue #7: "The command line is too long"
    • Improved performance on large input objects (30%)
  • Other
    • Code cleaning
    • Corrected Makefile

Version 1.2.1 (October 09, 2012)

japi-compliance-checker-1.2.1.tar.gz
This is a bug-fix release with a few new features.

Version 1.2 (September 22, 2012)

japi-compliance-checker-1.2.tar.gz
This is a bug-fix release with a few new features.

  • Bug Fixes
    • Fixed an issue with spaces in the execution path
  • Other
    • Code cleaning

Version 1.1.2 (June 08, 2012)

japi-compliance-checker-1.1.2.tar.gz
This is a bug-fix release with a few new features.

  • Bug Fixes
    • Fixed an issue with -classes-list option
    • Fixed an issue with spaces in the path to javap and other commands
    • Fixed an issue with escaping of inner class specifier
  • Other
    • Code cleaning

Version 1.1 (April 16, 2012)

japi-compliance-checker-1.1.tar.gz
Added many useful options, improved design of the report

  • New Features
    • Improved design of the compatibility report
    • Added compatibility rate in the summary of the report
    • Added "Other Changes in Data Types" section of the compatibility report
  • Other
    • Code cleaning

Version 1.0.3 (December 13, 2011)

japi-compliance-checker-1.0.3.tar.gz
This is a bug fix release.

  • New Features
    • Add Makefile.pl installer (1844)
  • Other
    • Corrected INSTALL file
    • Corrected help message
    • Changed license to dual GPL and LGPL of any version
    • Added check for presence of installed JDK-devel package in the system
    • Updated copyrights

Version 1.0 (August 30, 2011)

japi-compliance-checker-1.0.tar.gz
Initial prototype of the tool. Tested on 38 Java libraries.

japi-compliance-checker-1.3.5/doc/Descriptor.html000066400000000000000000000131131222675607600217420ustar00rootroot00000000000000 Java Library Descriptor

Java Library Descriptor

Java library descriptor is a simple XML-file that specifies version number, paths to Java ARchives and optionally some other information.

Primary Sections

 <version>
     /* Version of the library */
 </version>
 
 <archives>
     /* The list of paths to Java ARchives and/or
        directories with archives, one per line */
 </archives>

Optional Sections

 <skip_packages>
     /* The list of packages, that
        should not be checked, one per line */
 </skip_packages>
 
 <packages>
     /* The list of packages, that
        should be checked, one per line.
        Other packages will not be checked. */
 </packages>

Examples

  • JDO
 <version>
     2.2
 </version>
 
 <archives>
     installed/jdo/2.2/jdo2-api-2.2.jar
 </archives>
  • JDK
 <version>
     1.6.0
 </version>
 
 <archives>
     installed/jdk/1.6.0/jre/lib/jsse.jar
     installed/jdk/1.6.0/jre/lib/jce.jar
     installed/jdk/1.6.0/jre/lib/rt.jar
 </archives>
  • Jackrabbit
 <version>
     2.2.8
 </version>
 
 <archives>
     installed/jackrabbit/2.2.8/jackrabbit-standalone-2.2.8.jar
 </archives>
 
 <skip_packages>
     org.apache.jackrabbit.core
     org.apache.jackrabbit.standalone.cli.core
     org.apache.jackrabbit.test
     x2006.main
 </skip_packages>
 
 <packages>
     org.apache.jackrabbit
     org.apache.jcr
 </packages>
japi-compliance-checker-1.3.5/doc/Options.html000066400000000000000000000223711222675607600212650ustar00rootroot00000000000000 Java API Compliance Checker Options

Java API Compliance Checker Options

Contents

Information Options

  • -h|-help
Print help message.

  • -v|-version
Print version information.

  • -dumpversion
Print the tool version and don't do anything else.

General Options

  • -l|-lib|-library NAME
Library name (without version). It affects only on the path and the title of the report.

  • -d1|-old|-o PATH
Path to the Java ARchive or XML-descriptor of 1st (old) library version.

  • -d2|-new|-n PATH
Path to the Java ARchive or XML-descriptor of 2nd (new) library version.

Extra Options

  • -client|-app PATH
This option allows to specify the client Java ARchive that should be checked for portability to the newer library version.

  • -binary
Show "Binary" compatibility problems only. Generate report to "bin_compat_report.html" file.

  • -source
Show "Source" compatibility problems only. Generate report to "src_compat_report.html" file.

  • -check-implementation
Compare implementation code (method's body) of Java classes. Add "Changes in Implementation" section to the report.

  • -v1|-version1 NUM
Specify 1st library version outside the descriptor.

  • -v2|-version2 NUM
Specify 2nd library version outside the descriptor.

  • -s|-strict
Treat all compatibility warnings as problems.

  • -dump|-dump-api PATH
Create library API dump for the input descriptor. You can transfer it anywhere and pass instead of the descriptor. Also it may be used for debugging the tool.

  • -classes-list PATH
This option allows to specify a file with a list of classes that should be checked, other classes will not be checked.

  • -skip-deprecated
Skip analysis of deprecated methods and classes.

  • -skip-classes PATH
This option allows to specify a file with a list of classes that should not be checked.

  • -d|-template
Create XML descriptor template ./VERSION.xml

  • -report-path PATH
Path to compatibility report. Default: compat_reports/LIB_NAME/V1_to_V2/compat_report.html

  • -bin-report-path PATH
Path to "Binary" compatibility report. Default: compat_reports/LIB_NAME/V1_to_V2/bin_compat_report.html

  • -src-report-path PATH
Path to "Source" compatibility report. Default: compat_reports/LIB_NAME/V1_to_V2/src_compat_report.html

  • -quick
Quick analysis. Disabled: 1) analysis of method parameter names, 2) analysis of class field values, 3) analysis of usage of added abstract methods.

  • -sort
Enable sorting of data in API dumps.

  • -show-access
Show access level of non-public methods listed in the report.

  • -limit-affected LIMIT
The maximum number of affected methods listed under the description of the changed type in the report.

Other Options

  • -test
Create two incompatible versions of a sample library and run the tool to check them for compatibility. This option allows to check if the tool works correctly in the current environment.

  • -debug
Debugging mode. Print debug info on the screen. Save intermediate analysis stages in the debug directory: debug/LIB_NAME/VER/. Also consider using --dump option for debugging the tool.

  • -l-full|-lib-full NAME
Change library name in the report title to NAME. By default will be displayed a name specified by -l option.

  • -b|-browse PROGRAM
Open report(s) in the browser (firefox, opera, etc.).

  • -open
Open report(s) in the default browser.

japi-compliance-checker-1.3.5/doc/Readme.html000066400000000000000000000550161222675607600210310ustar00rootroot00000000000000 Java API Compliance Checker

Java API Compliance Checker

Java API Compliance Checker (Java ACC) is a tool for checking backward binary and source-level compatibility of a Java library API. The tool checks classes declarations of old and new versions and analyzes changes that may break compatibility: removed methods, removed class fields, added abstract methods, etc. The HTML compatibility report generated by this tool include separate sections for both source and binary compatibility analysis and exact error messages of the jvm and the compiler for each break found in the API. Binary incompatibility may result in crashing or incorrect behavior of existing clients built with an old version of a library when they are running with a new one. Source incompatibility may result in recompilation errors with a new library version. The tool is intended for developers of software libraries and maintainers of operating system who are interested in ensuring backward compatibility, i.e. allow old clients to run or to be recompiled with newer library versions.

See also:


Contents

Downloads

Releases

All releases can be downloaded from this page or github.com.

Latest release: 1.3.5

Git

Read-only access to the latest development version:
   git clone git://github.com/lvc/japi-compliance-checker.git  

License

This program is free software. You may use, redistribute and/or modify it under the terms of either the GNU GPL or LGPL.

Supported Platforms

GNU/Linux, FreeBSD, Mac OS X, MS Windows.

System Requirements

  • Linux, FreeBSD, Mac OS X

Detectable Compatibility Problems

The tool searches for the following list of changes in the API that may break binary/source-level compatibility. See this list of articles for more info.

  • Problems with Data Types
    • Classes and Interfaces
      • removed classes or interfaces
      • removed fields
      • removed methods
      • change of a field type
      • change of a field access level
      • change of a field attributes (final, static, etc.)
      • change of a constant field value
      • changes in fields (recursive analysis)
      • added/removed abstract methods
      • added/removed super-interfaces
    • Classes
      • added/removed super-classes
      • moving a method up class hierarchy
      • overridden methods
  • Problems with Methods
    • changed attributes (static, final, synchronized, abstract, etc.)
    • changed access level
    • added/removed exceptions
  • Problems with Implementation
    • changes in decompiled binary code


You can see detailed problem descriptions in the visual interactive HTML-format compatibility report (see this example) generated by the tool.

Limitations

The tool cannot check compatibility problems related to changes in code annotations.

Installation

The tool is ready-to-use after extracting the archive. You can also use a Makefile to install the tool into the system:
 cd japi-compliance-checker-x.y.z/ 

 sudo perl Makefile.pl -install --prefix=PREFIX [/usr, /usr/local, ...] 

This command will install a  japi-compliance-checker  program in the  PREFIX/bin  system directory.

To verify that the tool is installed correctly and it works on your host run:
 cd tmp/ 

 japi-compliance-checker -test 

Usage

Compare Java ARchives

Command to compare two java archives:
   japi-compliance-checker V1.jar V2.jar 

The compatibility report will be generated to:
   compat_reports/NAME/V1_to_V2/compat_report.html 

Compare Libraries

To compare different versions of a library that consists of many JARs you should create XML descriptors for two library versions: v1.xml and v2.xml files. Library descriptor is a simple XML-file that specifies version number, paths to Java ARchives and other optional information. An example of the descriptor is the following (1.0.xml):

 <version>
     1.0
 </version>
 
 <archives>
     /path1/to/JAR(s)/
     /path2/to/JAR(s)/
       ...
 </archives>

Command to compare two versions of a library:
   japi-compliance-checker -lib NAME -old V1.xml -new V2.xml 

The compatibility report will be generated to:
   compat_reports/NAME/V1_to_V2/compat_report.html 

Check Clients Portability

Command to check a client portability to a newer version of a Java library API:
   japi-compliance-checker -lib NAME -old V1.xml -new V2.xml -client CLIENT.jar 

Dump Library API to TXT file

Command to dump a Java library API to gzipped TXT format file:
   japi-compliance-checker -lib NAME -dump VER.xml 

The API dump will be generated to:
   api_dumps/NAME/NAME_VER.api.tar.gz 

Command-Line Options

See the list of all options on this page.

Examples

Check the JDO library API versions for compatibility:
   japi-compliance-checker jdo-api-2.0.jar jdo-api-3.0.jar 

The report will be generated to:
   compat_reports/jdo/2.0_to_3.0/compat_report.html 

Dump library API:
   japi-compliance-checker -l jdo -dump jdo-api-2.0.jar 

The API dump will be generated to:
   api_dumps/jdo/jdo_2.0.api.tar.gz 

Check client application portability between JDO versions:
   japi-compliance-checker -l jdo -old jdo-api-2.0.jar -new jdo-api-3.0.jar -client Teller.jar 

Report Format

The HTML-format compatibility report consists of:

  • Test Info - The library name and compared version numbers.
  • Summary - Verdict on compatibility. Number of archives, classes and methods checked by the tool.
  • Problem Summary - Classification of compatibility problems.
  • Added Methods - The list of added methods.
  • Removed Methods - The list of removed methods.
  • Problems with Data Types - List of compatibility problems caused by changes in data types (divided by the severity level: High, Medium, Low). List of affected methods.
  • Problems with Methods - The list of compatibility problems caused by changes in method parameters and attributes (divided by the severity level).
  • Other Changes in Data Types - The list of compatible changes in data types;
  • Other Changes in Methods - The list of compatible changes in methods;
  • Problems with Implementation - The list of changes in decompiled binary code. Use -check-implementation option to enable this section.

Examples of HTML-format report:

  • JDO: 2.2 to 3.0 API compatibility report
  • SLF4J: 1.5.11 to 1.6.0 binary compatibility report
  • Struts: 2.1.6 to 2.1.8 source compatibility report

Verdict on Compatibility

If the tool detected problems with high or medium level of severity or at least one removed method then the compatibility verdict is incompatible (otherwise compatible). Low-severity problems can be considered as warnings and don't affect the compatibility verdict unless the -strict option is specified.

Error Codes

Code Meaning
0 Compatible. The tool has run without any errors.
1 Incompatible. The tool has run without any errors.
2 Common error code (undifferentiated).
3 A system command is not found.
4 Cannot access input files.
7 Invalid input API dump.
8 Unsupported version of input API dump.
9 Cannot find a module.

Similar Tools

  1. SigTest - Oracle's SigTest signature testing and API conformance tool
  2. Clirr - checks Java libraries for binary and source compatibility with older releases
  3. Japitools - test for compatibility between Java APIs
  4. Jour - Java API compatibility testing tools
  5. Japi-checker - a java API backward compatibility checker which works at binary level

Bugs

Please send your bug reports, feature requests and questions to the the maintainer, post to issue tracker or mailing list.

Maintainers

The tool was originally developed by the Russian Linux Verification Center at ISPRAS and since 1.0.3 version it's developed by the ROSA Laboratory in Russia. Andrey Ponomarenko is the leader of this project.

Articles

  1. “Evolving Java-based APIs: Achieving API Binary Compatibility”, Jim des Rivières
  2. “Binary Compatibility”, Java Language Specification
  3. “Kinds of Compatibility: Source, Binary, and Behavioral”, Joseph D. Darcy's Oracle Weblog
japi-compliance-checker-1.3.5/japi-compliance-checker.pl000077500000000000000000010355341222675607600232020ustar00rootroot00000000000000#!/usr/bin/perl ########################################################################### # Java API Compliance Checker (Java ACC) 1.3.5 # A tool for checking backward compatibility of a Java library API # # Copyright (C) 2011 Institute for System Programming, RAS # Copyright (C) 2011 Russian Linux Verification Center # Copyright (C) 2011-2013 ROSA Lab # # Written by Andrey Ponomarenko # # PLATFORMS # ========= # Linux, FreeBSD, Mac OS X, MS Windows # # REQUIREMENTS # ============ # Linux, FreeBSD, Mac OS X # - JDK (javap, javac) # - Perl 5 (5.8 or newer) # # MS Windows # - JDK (javap, javac) # - Active Perl 5 (5.8 or newer) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License or the GNU Lesser # General Public License as published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # and the GNU Lesser General Public License along with this program. # If not, see . ########################################################################### use Getopt::Long; Getopt::Long::Configure ("posix_default", "no_ignore_case", "permute"); use File::Path qw(mkpath rmtree); use File::Temp qw(tempdir); use File::Copy qw(copy); use Cwd qw(abs_path cwd); use Data::Dumper; use Config; my $TOOL_VERSION = "1.3.5"; my $API_DUMP_VERSION = "1.0"; my $API_DUMP_MAJOR = majorVersion($API_DUMP_VERSION); my ($Help, $ShowVersion, %Descriptor, $TargetLibraryName, $CheckSeparately, $GenerateDescriptor, $TestSystem, $DumpAPI, $ClassListPath, $ClientPath, $StrictCompat, $DumpVersion, $BinaryOnly, $TargetLibraryFullName, $CheckImpl, %TargetVersion, $SourceOnly, $ShortMode, $KeepInternal, $OutputReportPath, $BinaryReportPath, $SourceReportPath, $Browse, $Debug, $Quick, $SortDump, $OpenReport, $SkipDeprecated, $SkipClasses, $ShowAccess, $AffectLimit); my $CmdName = get_filename($0); my $OSgroup = get_OSgroup(); my $ORIG_DIR = cwd(); my $TMP_DIR = tempdir(CLEANUP=>1); my $ARG_MAX = get_ARG_MAX(); my %OS_Archive = ( "windows"=>"zip", "default"=>"tar.gz" ); my %ERROR_CODE = ( # Compatible verdict "Compatible"=>0, "Success"=>0, # Incompatible verdict "Incompatible"=>1, # Undifferentiated error code "Error"=>2, # System command is not found "Not_Found"=>3, # Cannot access input files "Access_Error"=>4, # Invalid input API dump "Invalid_Dump"=>7, # Incompatible version of API dump "Dump_Version"=>8, # Cannot find a module "Module_Error"=>9 ); my %HomePage = ( "Wiki"=>"http://ispras.linuxbase.org/index.php/Java_API_Compliance_Checker", "Dev"=>"https://github.com/lvc/japi-compliance-checker" ); my $ShortUsage = "Java API Compliance Checker (Java ACC) $TOOL_VERSION A tool for checking backward compatibility of a Java library API Copyright (C) 2013 ROSA Lab License: GNU LGPL or GNU GPL Usage: $CmdName [options] Example: $CmdName OLD.jar NEW.jar More info: $CmdName --help"; if($#ARGV==-1) { printMsg("INFO", $ShortUsage); exit(0); } foreach (2 .. $#ARGV) { # correct comma separated options if($ARGV[$_-1] eq ",") { $ARGV[$_-2].=",".$ARGV[$_]; splice(@ARGV, $_-1, 2); } elsif($ARGV[$_-1]=~/,\Z/) { $ARGV[$_-1].=$ARGV[$_]; splice(@ARGV, $_, 1); } elsif($ARGV[$_]=~/\A,/ and $ARGV[$_] ne ",") { $ARGV[$_-1].=$ARGV[$_]; splice(@ARGV, $_, 1); } } GetOptions("h|help!" => \$Help, "v|version!" => \$ShowVersion, "dumpversion!" => \$DumpVersion, # general options "l|lib|library=s" => \$TargetLibraryName, "d1|old|o=s" => \$Descriptor{1}{"Path"}, "d2|new|n=s" => \$Descriptor{2}{"Path"}, # extra options "client|app=s" => \$ClientPath, "binary!" => \$BinaryOnly, "source!" => \$SourceOnly, "check-impl|check-implementation!" => \$CheckImpl, "v1|version1=s" => \$TargetVersion{1}, "v2|version2=s" => \$TargetVersion{2}, "s|strict!" => \$StrictCompat, "keep-internal!" => \$KeepInternal, "dump|dump-api=s" => \$DumpAPI, "classes-list=s" => \$ClassListPath, "skip-deprecated!" => \$SkipDeprecated, "skip-classes=s" => \$SkipClasses, "short" => \$ShortMode, "d|template!" => \$GenerateDescriptor, "report-path=s" => \$OutputReportPath, "bin-report-path=s" => \$BinaryReportPath, "src-report-path=s" => \$SourceReportPath, "quick!" => \$Quick, "sort!" => \$SortDump, "show-access!" => \$ShowAccess, "limit-affected=s" => \$AffectLimit, # other options "test!" => \$TestSystem, "debug!" => \$Debug, "l-full|lib-full=s" => \$TargetLibraryFullName, "b|browse=s" => \$Browse, "open!" => \$OpenReport ) or ERR_MESSAGE(); if(@ARGV) { if($#ARGV==1) { # japi-compliance-checker OLD.jar NEW.jar $Descriptor{1}{"Path"} = $ARGV[0]; $Descriptor{2}{"Path"} = $ARGV[1]; } else { ERR_MESSAGE(); } } sub ERR_MESSAGE() { printMsg("INFO", "\n".$ShortUsage); exit($ERROR_CODE{"Error"}); } my $AR_EXT = getAR_EXT($OSgroup); my $HelpMessage=" NAME: Java API Compliance Checker ($CmdName) Check backward compatibility of a Java library API DESCRIPTION: Java API Compliance Checker (Java ACC) is a tool for checking backward binary/source compatibility of a Java library API. The tool checks classes declarations of old and new versions and analyzes changes that may break compatibility: removed class members, added abstract methods, etc. Breakage of the binary compatibility may result in crashing or incorrect behavior of existing clients built with an old version of a library if they run with a new one. Breakage of the source compatibility may result in recompilation errors with a new library version. Java ACC is intended for library developers and operating system maintainers who are interested in ensuring backward compatibility (i.e. allow old clients to run or to be recompiled with a new version of a library). This tool is free software: you can redistribute it and/or modify it under the terms of the GNU LGPL or GNU GPL. USAGE: $CmdName [options] EXAMPLE: $CmdName OLD.jar NEW.jar OR $CmdName -lib NAME -old OLD.xml -new NEW.xml OLD.xml and NEW.xml are XML-descriptors: 1.0 /path1/to/JAR(s)/ /path2/to/JAR(s)/ ... INFORMATION OPTIONS: -h|-help Print this help. -v|-version Print version information. -dumpversion Print the tool version ($TOOL_VERSION) and don't do anything else. GENERAL OPTIONS: -l|-lib|-library NAME Library name (without version). -d1|-old|-o PATH Descriptor of 1st (old) library version. It may be one of the following: 1. Java ARchive (*.jar) 2. XML-descriptor (VERSION.xml file): 1.0 /path1/to/JAR(s)/ /path2/to/JAR(s)/ ... ... (XML-descriptor template may be generated by -d option) 3. API dump generated by -dump option 4. Directory with Java ARchives 5. Comma separated list of Java ARchives 6. Comma separated list of directories with Java ARchives If you are using 1, 4-6 descriptor types then you should specify version numbers with -v1 and -v2 options too. If you are using *.jar as a descriptor then the tool will try to get implementation version from MANIFEST.MF file. -d2|-new|-n PATH Descriptor of 2nd (new) library version. EXTRA OPTIONS: -client|-app PATH This option allows to specify the client Java ARchive that should be checked for portability to the new library version. -binary Show \"Binary\" compatibility problems only. Generate report to \"bin_compat_report.html\". -source Show \"Source\" compatibility problems only. Generate report to \"src_compat_report.html\". -check-impl|-check-implementation Compare implementation code (method\'s body) of Java classes. Add \'Problems with Implementation\' section to the report. -v1|-version1 NUM Specify 1st API version outside the descriptor. This option is needed if you have prefered an alternative descriptor type (see -d1 option). In general case you should specify it in the XML descriptor: VERSION -v2|-version2 NUM Specify 2nd library version outside the descriptor. -s|-strict Treat all API compatibility warnings as problems. -dump|-dump-api PATH Dump library API to gzipped TXT format file. You can transfer it anywhere and pass instead of the descriptor. Also it may be used for debugging the tool. Compatible dump versions: $API_DUMP_MAJOR.0<=V<=$API_DUMP_VERSION -classes-list PATH This option allows to specify a file with a list of classes that should be checked, other classes will not be checked. -skip-deprecated Skip analysis of deprecated methods and classes. -skip-classes PATH This option allows to specify a file with a list of classes that should not be checked. -short PATH Generate short report without 'Added Methods' section. -d|-template Create XML descriptor template ./VERSION.xml -report-path PATH Path to compatibility report. Default: compat_reports/LIB_NAME/V1_to_V2/compat_report.html -bin-report-path PATH Path to \"Binary\" compatibility report. Default: compat_reports/LIB_NAME/V1_to_V2/bin_compat_report.html -src-report-path PATH Path to \"Source\" compatibility report. Default: compat_reports/LIB_NAME/V1_to_V2/src_compat_report.html -quick Quick analysis. Disabled: - analysis of method parameter names - analysis of class field values - analysis of usage of added abstract methods - distinction of deprecated methods and classes -sort Enable sorting of data in API dumps. -show-access Show access level of non-public methods listed in the report. -limit-affected LIMIT The maximum number of affected methods listed under the description of the changed type in the report. OTHER OPTIONS: -test Run internal tests. Create two incompatible versions of a sample library and run the tool to check them for compatibility. This option allows to check if the tool works correctly in the current environment. -debug Debugging mode. Print debug info on the screen. Save intermediate analysis stages in the debug directory: debug/LIB_NAME/VER/ Also consider using --dump option for debugging the tool. -l-full|-lib-full NAME Change library name in the report title to NAME. By default will be displayed a name specified by -l option. -b|-browse PROGRAM Open report(s) in the browser (firefox, opera, etc.). -open Open report(s) in the default browser. REPORT: Compatibility report will be generated to: compat_reports/LIB_NAME/V1_to_V2/compat_report.html EXIT CODES: 0 - Compatible. The tool has run without any errors. non-zero - Incompatible or the tool has run with errors. REPORT BUGS TO: Andrey Ponomarenko MORE INFORMATION: ".$HomePage{"Wiki"}." ".$HomePage{"Dev"}."\n\n"; sub HELP_MESSAGE() { # -help printMsg("INFO", $HelpMessage."\n"); } my $Descriptor_Template = " /* Primary sections */ /* Version of the library */ /* The list of paths to Java ARchives and/or directories with archives, one per line */ /* Optional sections */ /* The list of packages, that should not be checked, one per line */ /* The list of packages, that should be checked, one per line. Other packages will not be checked. */ "; my %TypeProblems_Kind=( "Binary"=>{ "NonAbstract_Class_Added_Abstract_Method"=>"High", "Abstract_Class_Added_Abstract_Method"=>"Medium", "Class_Removed_Abstract_Method"=>"High", "Interface_Added_Abstract_Method"=>"Medium", "Interface_Removed_Abstract_Method"=>"High", "Removed_Class"=>"High", "Removed_Interface"=>"High", "Class_Method_Became_Abstract"=>"High", "Class_Method_Became_NonAbstract"=>"Low", "Added_Super_Class"=>"Low", "Abstract_Class_Added_Super_Abstract_Class"=>"Medium", "Removed_Super_Class"=>"Medium", "Changed_Super_Class"=>"Medium", "Abstract_Class_Added_Super_Interface"=>"Medium", "Class_Removed_Super_Interface"=>"High", "Interface_Added_Super_Interface"=>"Medium", "Interface_Added_Super_Constant_Interface"=>"Low", "Interface_Removed_Super_Interface"=>"High", "Class_Became_Interface"=>"High", "Interface_Became_Class"=>"High", "Class_Became_Final"=>"High", "Class_Became_Abstract"=>"High", "Class_Added_Field"=>"Safe", "Interface_Added_Field"=>"Safe", "Removed_NonConstant_Field"=>"High", "Removed_Constant_Field"=>"Low", "Renamed_Field"=>"High", "Renamed_Constant_Field"=>"Low", "Changed_Field_Type"=>"High", "Changed_Field_Access"=>"High", "Changed_Final_Field_Value"=>"Medium", "Field_Became_Final"=>"Medium", "Field_Became_NonFinal"=>"Low", "NonConstant_Field_Became_Static"=>"High", "NonConstant_Field_Became_NonStatic"=>"High", "Class_Overridden_Method"=>"Low", "Class_Method_Moved_Up_Hierarchy"=>"Low" }, "Source"=>{ "NonAbstract_Class_Added_Abstract_Method"=>"High", "Abstract_Class_Added_Abstract_Method"=>"High", "Interface_Added_Abstract_Method"=>"High", "Class_Removed_Abstract_Method"=>"High", "Interface_Removed_Abstract_Method"=>"High", "Removed_Class"=>"High", "Removed_Interface"=>"High", "Class_Method_Became_Abstract"=>"High", "Added_Super_Class"=>"Low", "Abstract_Class_Added_Super_Abstract_Class"=>"High", "Removed_Super_Class"=>"Medium", "Changed_Super_Class"=>"Medium", "Abstract_Class_Added_Super_Interface"=>"High", "Class_Removed_Super_Interface"=>"High", "Interface_Added_Super_Interface"=>"High", "Interface_Added_Super_Constant_Interface"=>"Low", "Interface_Removed_Super_Interface"=>"High", "Interface_Removed_Super_Constant_Interface"=>"High", "Class_Became_Interface"=>"High", "Interface_Became_Class"=>"High", "Class_Became_Final"=>"High", "Class_Became_Abstract"=>"High", "Class_Added_Field"=>"Safe", "Interface_Added_Field"=>"Safe", "Removed_NonConstant_Field"=>"High", "Removed_Constant_Field"=>"High", "Renamed_Field"=>"High", "Renamed_Constant_Field"=>"High", "Changed_Field_Type"=>"High", "Changed_Field_Access"=>"High", "Field_Became_Final"=>"Medium", "Constant_Field_Became_NonStatic"=>"High", "NonConstant_Field_Became_NonStatic"=>"High" } ); my %MethodProblems_Kind=( "Binary"=>{ "Added_Method"=>"Safe", "Removed_Method"=>"High", "Method_Became_Static"=>"High", "Method_Became_NonStatic"=>"High", "NonStatic_Method_Became_Final"=>"Medium", "Changed_Method_Access"=>"High", "Method_Became_Synchronized"=>"Low", "Method_Became_NonSynchronized"=>"Low", "Method_Became_Abstract"=>"High", "Method_Became_NonAbstract"=>"Low", "NonAbstract_Method_Added_Checked_Exception"=>"Low", "NonAbstract_Method_Removed_Checked_Exception"=>"Low", "Added_Unchecked_Exception"=>"Low", "Removed_Unchecked_Exception"=>"Low", "Variable_Arity_To_Array"=>"Low",# not implemented yet "Changed_Method_Return_From_Void"=>"High" }, "Source"=>{ "Added_Method"=>"Safe", "Removed_Method"=>"High", "Method_Became_Static"=>"Low", "Method_Became_NonStatic"=>"High", "Static_Method_Became_Final"=>"Medium", "NonStatic_Method_Became_Final"=>"Medium", "Changed_Method_Access"=>"High", "Method_Became_Abstract"=>"High", "Abstract_Method_Added_Checked_Exception"=>"Medium", "NonAbstract_Method_Added_Checked_Exception"=>"Medium", "Abstract_Method_Removed_Checked_Exception"=>"Medium", "NonAbstract_Method_Removed_Checked_Exception"=>"Medium" } ); my %KnownRuntimeExceptions= map {$_=>1} ( # To separate checked- and unchecked- exceptions "java.lang.AnnotationTypeMismatchException", "java.lang.ArithmeticException", "java.lang.ArrayStoreException", "java.lang.BufferOverflowException", "java.lang.BufferUnderflowException", "java.lang.CannotRedoException", "java.lang.CannotUndoException", "java.lang.ClassCastException", "java.lang.CMMException", "java.lang.ConcurrentModificationException", "java.lang.DataBindingException", "java.lang.DOMException", "java.lang.EmptyStackException", "java.lang.EnumConstantNotPresentException", "java.lang.EventException", "java.lang.IllegalArgumentException", "java.lang.IllegalMonitorStateException", "java.lang.IllegalPathStateException", "java.lang.IllegalStateException", "java.lang.ImagingOpException", "java.lang.IncompleteAnnotationException", "java.lang.IndexOutOfBoundsException", "java.lang.JMRuntimeException", "java.lang.LSException", "java.lang.MalformedParameterizedTypeException", "java.lang.MirroredTypeException", "java.lang.MirroredTypesException", "java.lang.MissingResourceException", "java.lang.NegativeArraySizeException", "java.lang.NoSuchElementException", "java.lang.NoSuchMechanismException", "java.lang.NullPointerException", "java.lang.ProfileDataException", "java.lang.ProviderException", "java.lang.RasterFormatException", "java.lang.RejectedExecutionException", "java.lang.SecurityException", "java.lang.SystemException", "java.lang.TypeConstraintException", "java.lang.TypeNotPresentException", "java.lang.UndeclaredThrowableException", "java.lang.UnknownAnnotationValueException", "java.lang.UnknownElementException", "java.lang.UnknownEntityException", "java.lang.UnknownTypeException", "java.lang.UnmodifiableSetException", "java.lang.UnsupportedOperationException", "java.lang.WebServiceException", "java.lang.WrongMethodTypeException" ); my %Slash_Type=( "default"=>"/", "windows"=>"\\" ); my $SLASH = $Slash_Type{$OSgroup}?$Slash_Type{$OSgroup}:$Slash_Type{"default"}; my %OS_AddPath=( # this data needed if tool can't detect it automatically "macos"=>{ "bin"=>{"/Developer/usr/bin"=>1}}, "beos"=>{ "bin"=>{"/boot/common/bin"=>1,"/boot/system/bin"=>1,"/boot/develop/abi"=>1}} ); #Global variables my %RESULT; my $ExtractCounter = 0; my %Cache; my $TOP_REF = "to the top"; my %DEBUG_PATH; #Types my %TypeInfo; my $TypeID = 0; my %CheckedTypes; my %TName_Tid; my %Class_Constructed; #Classes my %ClassList_User; my %SkipClass_User; my %UsedMethods_Client; my %UsedFields_Client; my %LibClasses; my %LibArchives; my %Class_Methods; my %Class_AbstractMethods; my %Class_Fields; my %MethodInvoked; my %ClassMethod_AddedInvoked; my %FieldUsed; #Methods my %CheckedMethods; my %MethodBody; my %tr_name; #Merging my %MethodInfo; my $Version; my %AddedMethod_Abstract; my %RemovedMethod_Abstract; my %ChangedReturnFromVoid; my %SkipPackages; my %KeepPackages; #Report my %Type_MaxPriority; #Recursion locks my @RecurSymlink; my @RecurTypes; #System my %SystemPaths; my %DefaultBinPaths; #Problem descriptions my %CompatProblems; my %ImplProblems; my %TotalAffected; #Rerort my $ContentID = 1; my $ContentSpanStart = "\n"; my $ContentSpanStart_Affected = "\n"; my $ContentSpanEnd = "\n"; my $ContentDivStart = "
\n"; my $ContentDivEnd = "
\n"; my $Content_Counter = 0; #Modes my $JoinReport = 1; my $DoubleReport = 0; sub get_CmdPath($) { my $Name = $_[0]; return "" if(not $Name); if(defined $Cache{"get_CmdPath"}{$Name}) { return $Cache{"get_CmdPath"}{$Name}; } my $Path = search_Cmd($Name); if(not $Path and $OSgroup eq "windows") { # search for *.exe file $Path=search_Cmd($Name.".exe"); } if($Path=~/\s/) { $Path = "\"".$Path."\""; } return ($Cache{"get_CmdPath"}{$Name} = $Path); } sub search_Cmd($) { my $Name = $_[0]; return "" if(not $Name); if(defined $Cache{"search_Cmd"}{$Name}) { return $Cache{"search_Cmd"}{$Name}; } if(my $DefaultPath = get_CmdPath_Default($Name)) { return ($Cache{"search_Cmd"}{$Name} = $DefaultPath); } foreach my $Path (sort {length($a)<=>length($b)} keys(%{$SystemPaths{"bin"}})) { if(-f $Path."/".$Name or -f $Path."/".$Name.".exe") { return ($Cache{"search_Cmd"}{$Name} = joinPath($Path,$Name)); } } return ($Cache{"search_Cmd"}{$Name} = ""); } sub get_CmdPath_Default($) { # search in PATH return "" if(not $_[0]); if(defined $Cache{"get_CmdPath_Default"}{$_[0]}) { return $Cache{"get_CmdPath_Default"}{$_[0]}; } return ($Cache{"get_CmdPath_Default"}{$_[0]} = get_CmdPath_Default_I($_[0])); } sub get_CmdPath_Default_I($) { # search in PATH my $Name = $_[0]; if($Name=~/find/) { # special case: search for "find" utility if(`find \"$TMP_DIR\" -maxdepth 0 2>\"$TMP_DIR/null\"`) { return "find"; } } if(get_version($Name)) { return $Name; } if($OSgroup eq "windows") { if(`$Name /? 2>\"$TMP_DIR/null\"`) { return $Name; } } if($Name!~/which/) { if(my $WhichCmd = get_CmdPath("which")) { if(`$WhichCmd $Name 2>\"$TMP_DIR/null\"`) { return $Name; } } } foreach my $Path (sort {length($a)<=>length($b)} keys(%DefaultBinPaths)) { if(-f $Path."/".$Name) { return joinPath($Path,$Name); } } return ""; } sub showPos($) { my $Number = $_[0]; if(not $Number) { $Number = 1; } else { $Number = int($Number)+1; } if($Number>3) { return $Number."th"; } elsif($Number==1) { return "1st"; } elsif($Number==2) { return "2nd"; } elsif($Number==3) { return "3rd"; } else { return $Number; } } sub getAR_EXT($) { my $Target = $_[0]; if(my $Ext = $OS_Archive{$Target}) { return $Ext; } return $OS_Archive{"default"}; } sub readDescriptor($$) { my ($LibVersion, $Content) = @_; return if(not $LibVersion); my $DName = $DumpAPI?"descriptor":"descriptor \"d$LibVersion\""; if(not $Content) { exitStatus("Error", "$DName is empty"); } if($Content!~/\//g; $Descriptor{$LibVersion}{"Version"} = parseTag(\$Content, "version"); $Descriptor{$LibVersion}{"Version"} = $TargetVersion{$LibVersion} if($TargetVersion{$LibVersion}); if(not $Descriptor{$LibVersion}{"Version"}) { exitStatus("Error", "version in the $DName is not specified ( section)"); } my $DArchives = parseTag(\$Content, "archives"); if(not $DArchives){ exitStatus("Error", "Java ARchives in the $DName are not specified ( section)"); } else {# append the descriptor Java ARchives list if($Descriptor{$LibVersion}{"Archives"}) { $Descriptor{$LibVersion}{"Archives"} .= "\n".$DArchives; } else { $Descriptor{$LibVersion}{"Archives"} = $DArchives; } foreach my $Path (split(/\s*\n\s*/, $DArchives)) { if(not -e $Path) { exitStatus("Access_Error", "can't access \'$Path\'"); } } } foreach my $Package (split(/\s*\n\s*/, parseTag(\$Content, "skip_packages"))) { $SkipPackages{$LibVersion}{$Package} = 1; } foreach my $Package (split(/\s*\n\s*/, parseTag(\$Content, "packages"))) { $KeepPackages{$LibVersion}{$Package} = 1; } } sub parseTag($$) { my ($CodeRef, $Tag) = @_; return "" if(not $CodeRef or not ${$CodeRef} or not $Tag); if(${$CodeRef}=~s/\<\Q$Tag\E\>((.|\n)+?)\<\/\Q$Tag\E\>//) { my $Content = $1; $Content=~s/(\A\s+|\s+\Z)//g; return $Content; } else { return ""; } } sub ignore_path($$) { my ($Path, $Prefix) = @_; return 1 if(not $Path or not -e $Path or not $Prefix or not -e $Prefix); return 1 if($Path=~/\~\Z/);# skipping system backup files # skipping hidden .svn, .git, .bzr, .hg and CVS directories return 1 if(cut_path_prefix($Path, $Prefix)=~/(\A|[\/\\]+)(\.(svn|git|bzr|hg)|CVS)([\/\\]+|\Z)/); return 0; } sub cut_path_prefix($$) { my ($Path, $Prefix) = @_; $Prefix=~s/[\/\\]+\Z//; $Path=~s/\A\Q$Prefix\E([\/\\]+|\Z)//; return $Path; } sub get_filename($) { # much faster than basename() from File::Basename module if(defined $Cache{"get_filename"}{$_[0]}) { return $Cache{"get_filename"}{$_[0]}; } if($_[0] and $_[0]=~/([^\/\\]+)[\/\\]*\Z/) { return ($Cache{"get_filename"}{$_[0]}=$1); } return ($Cache{"get_filename"}{$_[0]}=""); } sub get_dirname($) { # much faster than dirname() from File::Basename module if(defined $Cache{"get_dirname"}{$_[0]}) { return $Cache{"get_dirname"}{$_[0]}; } if($_[0] and $_[0]=~/\A(.*?)[\/\\]+[^\/\\]*[\/\\]*\Z/) { return ($Cache{"get_dirname"}{$_[0]}=$1); } return ($Cache{"get_dirname"}{$_[0]}=""); } sub separate_path($) { return (get_dirname($_[0]), get_filename($_[0])); } sub esc($) { my $Str = $_[0]; $Str=~s/([()\[\]{}$ &'"`;,<>\+])/\\$1/g; return $Str; } sub get_Signature($$$) { my ($Method, $LibVersion, $Kind) = @_; if(defined $Cache{"get_Signature"}{$LibVersion}{$Method}{$Kind}) { return $Cache{"get_Signature"}{$LibVersion}{$Method}{$Kind}; } my $Signature = $MethodInfo{$LibVersion}{$Method}{"ShortName"}; if($Kind eq "Full") { $Signature = get_TypeName($MethodInfo{$LibVersion}{$Method}{"Class"}, $LibVersion).".".$Signature; } my @Params = (); foreach my $PPos (sort {int($a)<=>int($b)} keys(%{$MethodInfo{$LibVersion}{$Method}{"Param"}})) { my $PTid = $MethodInfo{$LibVersion}{$Method}{"Param"}{$PPos}{"Type"}; if(my $PTName = get_TypeName($PTid, $LibVersion)) { if($Kind eq "Full") { my $PName = $MethodInfo{$LibVersion}{$Method}{"Param"}{$PPos}{"Name"}; push(@Params, $PTName." ".$PName); } else { push(@Params, $PTName); } } } $Signature .= "(".join(", ", @Params).")"; if($Kind eq "Full") { if($MethodInfo{$LibVersion}{$Method}{"Static"}) { $Signature .= " [static]"; } elsif($MethodInfo{$LibVersion}{$Method}{"Abstract"}) { $Signature .= " [abstract]"; } if($ShowAccess) { if(my $Access = $MethodInfo{$LibVersion}{$Method}{"Access"}) { if($Access ne "public") { $Signature .= " [".$Access."]"; } } } if(my $ReturnId = $MethodInfo{$LibVersion}{$Method}{"Return"}) { $Signature .= " :".get_TypeName($ReturnId, $LibVersion); } $Signature=~s/java\.lang\.//g; } $Cache{"get_Signature"}{$LibVersion}{$Method}{$Kind} = $Signature; return $Cache{"get_Signature"}{$LibVersion}{$Method}{$Kind}; } sub joinPath($$) { return join($SLASH, @_); } sub get_abs_path($) { # abs_path() should NOT be called for absolute inputs # because it can change them my $Path = $_[0]; if(not is_abs($Path)) { $Path = abs_path($Path); } return $Path; } sub is_abs($) { return ($_[0]=~/\A(\/|\w+:[\/\\])/); } sub cmd_find($$$$) { my ($Path, $Type, $Name, $MaxDepth) = @_; return () if(not $Path or not -e $Path); if($OSgroup eq "windows") { my $DirCmd = get_CmdPath("dir"); if(not $DirCmd) { exitStatus("Not_Found", "can't find \"dir\" command"); } $Path=~s/[\\]+\Z//; $Path = get_abs_path($Path); my $Cmd = $DirCmd." \"$Path\" /B /O"; if($MaxDepth!=1) { $Cmd .= " /S"; } if($Type eq "d") { $Cmd .= " /AD"; } my @Files = (); if($Name) { # FIXME: how to search file names in MS shell? $Name=~s/\*/.*/g if($Name!~/\]/); foreach my $File (split(/\n/, `$Cmd`)) { if($File=~/$Name\Z/i) { push(@Files, $File); } } } else { @Files = split(/\n/, `$Cmd 2>\"$TMP_DIR/null\"`); } my @AbsPaths = (); foreach my $File (@Files) { if(not is_abs($File)) { $File = joinPath($Path, $File); } if($Type eq "f" and not -f $File) { # skip dirs next; } push(@AbsPaths, $File); } if($Type eq "d") { push(@AbsPaths, $Path); } return @AbsPaths; } else { my $FindCmd = get_CmdPath("find"); if(not $FindCmd) { exitStatus("Not_Found", "can't find a \"find\" command"); } $Path = get_abs_path($Path); if(-d $Path and -l $Path and $Path!~/\/\Z/) { # for directories that are symlinks $Path.="/"; } my $Cmd = $FindCmd." \"$Path\""; if($MaxDepth) { $Cmd .= " -maxdepth $MaxDepth"; } if($Type) { $Cmd .= " -type $Type"; } if($Name) { if($Name=~/\]/) { $Cmd .= " -regex \"$Name\""; } else { $Cmd .= " -name \"$Name\""; } } return split(/\n/, `$Cmd 2>\"$TMP_DIR/null\"`); } } sub path_format($$) { # forward slash to pass into MinGW GCC my ($Path, $Fmt) = @_; if($Fmt eq "windows") { $Path=~s/\//\\/g; $Path=lc($Path); } else { $Path=~s/\\/\//g; } return $Path; } sub unpackDump($) { my $Path = $_[0]; return "" if(not $Path or not -e $Path); $Path = get_abs_path($Path); $Path = path_format($Path, $OSgroup); my ($Dir, $FileName) = separate_path($Path); my $UnpackDir = $TMP_DIR."/unpack"; rmtree($UnpackDir); mkpath($UnpackDir); if($FileName=~s/\Q.zip\E\Z//g) { # *.zip my $UnzipCmd = get_CmdPath("unzip"); if(not $UnzipCmd) { exitStatus("Not_Found", "can't find \"unzip\" command"); } chdir($UnpackDir); system("$UnzipCmd \"$Path\" >contents.txt"); if($?) { exitStatus("Error", "can't extract \'$Path\'"); } chdir($ORIG_DIR); my @Contents = (); foreach (split("\n", readFile("$UnpackDir/contents.txt"))) { if(/inflating:\s*([^\s]+)/) { push(@Contents, $1); } } if(not @Contents) { exitStatus("Error", "can't extract \'$Path\'"); } return joinPath($UnpackDir, $Contents[0]); } elsif($FileName=~s/\Q.tar.gz\E\Z//g) { # *.tar.gz if($OSgroup eq "windows") { # -xvzf option is not implemented in tar.exe (2003) # use "gzip.exe -k -d -f" + "tar.exe -xvf" instead my $TarCmd = get_CmdPath("tar"); if(not $TarCmd) { exitStatus("Not_Found", "can't find \"tar\" command"); } my $GzipCmd = get_CmdPath("gzip"); if(not $GzipCmd) { exitStatus("Not_Found", "can't find \"gzip\" command"); } chdir($UnpackDir); system("$GzipCmd -k -d -f \"$Path\"");# keep input files (-k) if($?) { exitStatus("Error", "can't extract \'$Path\'"); } system("$TarCmd -xvf \"$Dir\\$FileName.tar\" >contents.txt"); if($?) { exitStatus("Error", "can't extract \'$Path\'"); } chdir($ORIG_DIR); unlink($Dir."/".$FileName.".tar"); my @Contents = split("\n", readFile("$UnpackDir/contents.txt")); if(not @Contents) { exitStatus("Error", "can't extract \'$Path\'"); } return joinPath($UnpackDir, $Contents[0]); } else { # Unix my $TarCmd = get_CmdPath("tar"); if(not $TarCmd) { exitStatus("Not_Found", "can't find \"tar\" command"); } chdir($UnpackDir); system("$TarCmd -xvzf \"$Path\" >contents.txt"); if($?) { exitStatus("Error", "can't extract \'$Path\'"); } chdir($ORIG_DIR); # The content file name may be different # from the package file name my @Contents = split("\n", readFile("$UnpackDir/contents.txt")); if(not @Contents) { exitStatus("Error", "can't extract \'$Path\'"); } return joinPath($UnpackDir, $Contents[0]); } } } sub mergeClasses() { foreach my $ClassName (keys(%{$Class_Methods{1}})) { next if(not $ClassName); my $Type1_Id = $TName_Tid{1}{$ClassName}; my %Type1 = get_Type($Type1_Id, 1); if(defined $Type1{"Access"} and $Type1{"Access"}=~/private/) { next; } my $Type2_Id = $TName_Tid{2}{$ClassName}; if(not $Type2_Id) { foreach my $Method (keys(%{$Class_Methods{1}{$ClassName}})) { # removed classes/interfaces with public methods next if(not methodFilter($Method, 1)); $CheckedTypes{$ClassName} = 1; $CheckedMethods{$Method} = 1; if($Type1{"Type"} eq "class") { %{$CompatProblems{$Method}{"Removed_Class"}{"this"}} = ( "Type_Name"=>$ClassName, "Target"=>$ClassName ); } else { %{$CompatProblems{$Method}{"Removed_Interface"}{"this"}} = ( "Type_Name"=>$ClassName, "Target"=>$ClassName ); } } } } } sub findFieldPair($$) { my ($Field_Pos, $Pair_Type) = @_; foreach my $Pair_Name (keys(%{$Pair_Type->{"Fields"}})) { if(defined $Pair_Type->{"Fields"}{$Pair_Name}) { if($Pair_Type->{"Fields"}{$Pair_Name}{"Pos"} eq $Field_Pos) { return $Pair_Name; } } } return "lost"; } my %Severity_Val=( "High"=>3, "Medium"=>2, "Low"=>1, "Safe"=>-1 ); sub maxSeverity($$) { my ($S1, $S2) = @_; if(cmpSeverities($S1, $S2)) { return $S1; } else { return $S2; } } sub cmpSeverities($$) { my ($S1, $S2) = @_; if(not $S1) { return 0; } elsif(not $S2) { return 1; } return ($Severity_Val{$S1}>$Severity_Val{$S2}); } sub getProblemSeverity($$$$) { my ($Level, $Kind, $TypeName, $Target) = @_; if($Level eq "Source") { if($TypeProblems_Kind{$Level}{$Kind}) { return $TypeProblems_Kind{$Level}{$Kind}; } elsif($MethodProblems_Kind{$Level}{$Kind}) { return $MethodProblems_Kind{$Level}{$Kind}; } } elsif($Level eq "Binary") { if($Kind eq "Interface_Added_Abstract_Method" or $Kind eq "Abstract_Class_Added_Abstract_Method") { if(not keys(%{$MethodInvoked{2}{$Target}})) { if($Quick) { return "Low"; } else { return "Safe"; } } } elsif($Kind eq "Interface_Added_Super_Interface" or $Kind eq "Abstract_Class_Added_Super_Interface" or $Kind eq "Abstract_Class_Added_Super_Abstract_Class") { if(not keys(%{$ClassMethod_AddedInvoked{$TypeName}})) { if($Quick) { return "Low"; } else { return "Safe"; } } } elsif($Kind eq "Changed_Final_Field_Value") { if($Target=~/(\A|_)(VERSION|VERNUM|BUILDNUMBER|BUILD)(_|\Z)/i) { return "Low"; } } if($TypeProblems_Kind{$Level}{$Kind}) { return $TypeProblems_Kind{$Level}{$Kind}; } elsif($MethodProblems_Kind{$Level}{$Kind}) { return $MethodProblems_Kind{$Level}{$Kind}; } } return "Low"; } sub isRecurType($$) { foreach (@RecurTypes) { if($_->{"Tid1"} eq $_[0] and $_->{"Tid2"} eq $_[1]) { return 1; } } return 0; } sub pushType($$) { my %TypeDescriptor=( "Tid1" => $_[0], "Tid2" => $_[1] ); push(@RecurTypes, \%TypeDescriptor); } sub get_SFormat($) { my $Name = $_[0]; $Name=~s/\./\//g; return $Name; } sub get_PFormat($) { my $Name = $_[0]; $Name=~s/\//./g; return $Name; } sub get_ConstantValue($$) { my ($Value, $ValueType) = @_; return "" if(not $Value); if($Value eq "\@EMPTY_STRING\@") { return "\"\""; } elsif($ValueType eq "java.lang.String") { return "\"".$Value."\""; } else { return $Value; } } sub mergeTypes($$) { my ($Type1_Id, $Type2_Id) = @_; return () if(not $Type1_Id or not $Type2_Id); my (%Sub_SubProblems, %SubProblems) = (); return %{$Cache{"mergeTypes"}{$Type1_Id}{$Type2_Id}} if($Cache{"mergeTypes"}{$Type1_Id}{$Type2_Id}); my %Type1 = get_Type($Type1_Id, 1); my %Type2 = get_Type($Type2_Id, 2); if(isRecurType($Type1_Id, $Type2_Id)) { # do not follow to recursive declarations return (); } return () if(not $Type1{"Name"} or not $Type2{"Name"}); return () if(not $Type1{"Archive"} or not $Type2{"Archive"}); return () if($Type1{"Name"} ne $Type2{"Name"}); return () if(skip_package($Type1{"Package"}, 1)); $CheckedTypes{$Type1{"Name"}} = 1; if($Type1{"BaseType"} and $Type2{"BaseType"}) { # check base type (arrays) return mergeTypes($Type1{"BaseType"}, $Type2{"BaseType"}); } return () if($Type2{"Type"}!~/(class|interface)/); if($Type1{"Type"} eq "class" and not $Class_Constructed{1}{$Type1_Id}) { # class cannot be constructed or inherited by clients return (); } if($Type1{"Type"} eq "class" and $Type2{"Type"} eq "interface") { %{$SubProblems{"Class_Became_Interface"}{""}}=( "Type_Name"=>$Type1{"Name"} ); %{$Cache{"mergeTypes"}{$Type1_Id}{$Type2_Id}} = %SubProblems; pop(@RecurTypes); return %SubProblems; } if($Type1{"Type"} eq "interface" and $Type2{"Type"} eq "class") { %{$SubProblems{"Interface_Became_Class"}{""}}=( "Type_Name"=>$Type1{"Name"} ); %{$Cache{"mergeTypes"}{$Type1_Id}{$Type2_Id}} = %SubProblems; pop(@RecurTypes); return %SubProblems; } if(not $Type1{"Final"} and $Type2{"Final"}) { %{$SubProblems{"Class_Became_Final"}{""}}=( "Type_Name"=>$Type1{"Name"}, "Target"=>$Type1{"Name"} ); } if(not $Type1{"Abstract"} and $Type2{"Abstract"}) { %{$SubProblems{"Class_Became_Abstract"}{""}}=( "Type_Name"=>$Type1{"Name"} ); } pushType($Type1_Id, $Type2_Id); foreach my $AddedMethod (keys(%{$AddedMethod_Abstract{$Type1{"Name"}}})) { if($Type1{"Type"} eq "class") { if($Type1{"Abstract"}) { my $Add_Effect = ""; if(my @InvokedBy = keys(%{$MethodInvoked{2}{$AddedMethod}})) { my $MFirst = $InvokedBy[0]; $Add_Effect = " Added abstract method is called in 2nd library version by the method ".black_name($MethodInfo{1}{$MFirst}{"Signature"})." and may not be implemented by old clients."; } %{$SubProblems{"Abstract_Class_Added_Abstract_Method"}{get_SFormat($AddedMethod)}} = ( "Type_Name"=>$Type1{"Name"}, "Type_Type"=>$Type1{"Type"}, "Target"=>$AddedMethod, "Add_Effect"=>$Add_Effect ); } else { %{$SubProblems{"NonAbstract_Class_Added_Abstract_Method"}{get_SFormat($AddedMethod)}} = ( "Type_Name"=>$Type1{"Name"}, "Type_Type"=>$Type1{"Type"}, "Target"=>$AddedMethod ); } } else { my $Add_Effect = ""; if(my @InvokedBy = keys(%{$MethodInvoked{2}{$AddedMethod}})) { my $MFirst = $InvokedBy[0]; $Add_Effect = " Added abstract method is called in 2nd library version by the method ".black_name($MethodInfo{1}{$MFirst}{"Signature"})." and may not be implemented by old clients."; } %{$SubProblems{"Interface_Added_Abstract_Method"}{get_SFormat($AddedMethod)}} = ( "Type_Name"=>$Type1{"Name"}, "Type_Type"=>$Type1{"Type"}, "Target"=>$AddedMethod, "Add_Effect"=>$Add_Effect ); } } foreach my $RemovedMethod (keys(%{$RemovedMethod_Abstract{$Type1{"Name"}}})) { if($Type1{"Type"} eq "class") { %{$SubProblems{"Class_Removed_Abstract_Method"}{get_SFormat($RemovedMethod)}} = ( "Type_Name"=>$Type1{"Name"}, "Type_Type"=>$Type1{"Type"}, "Target"=>$RemovedMethod ); } else { %{$SubProblems{"Interface_Removed_Abstract_Method"}{get_SFormat($RemovedMethod)}} = ( "Type_Name"=>$Type1{"Name"}, "Type_Type"=>$Type1{"Type"}, "Target"=>$RemovedMethod ); } } if($Type1{"Type"} eq "class" and $Type2{"Type"} eq "class") { my %SuperClass1 = get_Type($Type1{"SuperClass"}, 1); my %SuperClass2 = get_Type($Type2{"SuperClass"}, 2); if($SuperClass2{"Name"} ne $SuperClass1{"Name"}) { if($SuperClass1{"Name"} eq "java.lang.Object" or not $SuperClass1{"Name"}) { # Java 6: java.lang.Object # Java 7: none if($SuperClass2{"Abstract"} and $Type1{"Abstract"} and $Type2{"Abstract"} and keys(%{$Class_AbstractMethods{2}{$SuperClass2{"Name"}}})) { my $Add_Effect = ""; if(my @Invoked = keys(%{$ClassMethod_AddedInvoked{$Type1{"Name"}}})) { my $MFirst = $Invoked[0]; my $MSignature = unmangle($MFirst); $MSignature=~s/\A.+\.(\w+\()/$1/g; # short name my $InvokedBy = $ClassMethod_AddedInvoked{$Type1{"Name"}}{$MFirst}; $Add_Effect = " Abstract method ".black_name($MSignature)." from the added abstract super-class is called by the method ".black_name($MethodInfo{2}{$InvokedBy}{"Signature"})." in 2nd library version and may not be implemented by old clients."; } %{$SubProblems{"Abstract_Class_Added_Super_Abstract_Class"}{""}} = ( "Type_Name"=>$Type1{"Name"}, "Target"=>$SuperClass2{"Name"}, "Add_Effect"=>$Add_Effect ); } else { %{$SubProblems{"Added_Super_Class"}{""}} = ( "Type_Name"=>$Type1{"Name"}, "Target"=>$SuperClass2{"Name"} ); } } elsif($SuperClass2{"Name"} eq "java.lang.Object" or not $SuperClass2{"Name"}) { # Java 6: java.lang.Object # Java 7: none %{$SubProblems{"Removed_Super_Class"}{""}} = ( "Type_Name"=>$Type1{"Name"}, "Target"=>$SuperClass1{"Name"} ); } else { %{$SubProblems{"Changed_Super_Class"}{""}} = ( "Type_Name"=>$Type1{"Name"}, "Target"=>$SuperClass1{"Name"}, "Old_Value"=>$SuperClass1{"Name"}, "New_Value"=>$SuperClass2{"Name"} ); } } } my %SuperInterfaces_Old = map {get_TypeName($_, 1) => 1} keys(%{$Type1{"SuperInterface"}}); my %SuperInterfaces_New = map {get_TypeName($_, 2) => 1} keys(%{$Type2{"SuperInterface"}}); foreach my $SuperInterface (keys(%SuperInterfaces_New)) { if(not $SuperInterfaces_Old{$SuperInterface}) { if($Type1{"Type"} eq "interface") { if(keys(%{$Class_AbstractMethods{2}{$SuperInterface}}) or $SuperInterface=~/\Ajava\./) { my $Add_Effect = ""; if(my @Invoked = keys(%{$ClassMethod_AddedInvoked{$Type1{"Name"}}})) { my $MFirst = $Invoked[0]; my $MSignature = unmangle($MFirst); $MSignature=~s/\A.+\.(\w+\()/$1/g; # short name my $InvokedBy = $ClassMethod_AddedInvoked{$Type1{"Name"}}{$MFirst}; $Add_Effect = " Abstract method ".black_name($MSignature)." from the added super-interface is called by the method ".black_name($MethodInfo{2}{$InvokedBy}{"Signature"})." in 2nd library version and may not be implemented by old clients."; } %{$SubProblems{"Interface_Added_Super_Interface"}{get_SFormat($SuperInterface)}} = ( "Type_Name"=>$Type1{"Name"}, "Target"=>$SuperInterface, "Add_Effect"=>$Add_Effect ); } elsif(keys(%{$Class_Fields{2}{$SuperInterface}})) { %{$SubProblems{"Interface_Added_Super_Constant_Interface"}{get_SFormat($SuperInterface)}} = ( "Type_Name"=>$Type1{"Name"}, "Target"=>$SuperInterface ); } else { # ??? } } else { if($Type1{"Abstract"} and $Type2{"Abstract"}) { my $Add_Effect = ""; if(my @Invoked = keys(%{$ClassMethod_AddedInvoked{$Type1{"Name"}}})) { my $MFirst = $Invoked[0]; my $MSignature = unmangle($MFirst); $MSignature=~s/\A.+\.(\w+\()/$1/g; # short name my $InvokedBy = $ClassMethod_AddedInvoked{$Type1{"Name"}}{$MFirst}; $Add_Effect = " Abstract method ".black_name($MSignature)." from the added super-interface is called by the method ".black_name($MethodInfo{2}{$InvokedBy}{"Signature"})." in 2nd library version and may not be implemented by old clients."; } %{$SubProblems{"Abstract_Class_Added_Super_Interface"}{get_SFormat($SuperInterface)}} = ( "Type_Name"=>$Type1{"Name"}, "Target"=>$SuperInterface, "Add_Effect"=>$Add_Effect ); } } } } foreach my $SuperInterface (keys(%SuperInterfaces_Old)) { if(not $SuperInterfaces_New{$SuperInterface}) { if($Type1{"Type"} eq "interface") { if(keys(%{$Class_AbstractMethods{1}{$SuperInterface}}) or $SuperInterface=~/\Ajava\./) { %{$SubProblems{"Interface_Removed_Super_Interface"}{get_SFormat($SuperInterface)}} = ( "Type_Name"=>$Type1{"Name"}, "Type_Type"=>"interface", "Target"=>$SuperInterface ); } elsif(keys(%{$Class_Fields{1}{$SuperInterface}})) { %{$SubProblems{"Interface_Removed_Super_Constant_Interface"}{get_SFormat($SuperInterface)}} = ( "Type_Name"=>$Type1{"Name"}, "Target"=>$SuperInterface ); } else { # ??? } } else { %{$SubProblems{"Class_Removed_Super_Interface"}{get_SFormat($SuperInterface)}} = ( "Type_Name"=>$Type1{"Name"}, "Type_Type"=>"class", "Target"=>$SuperInterface ); } } } foreach my $Field_Name (keys(%{$Type1{"Fields"}})) {# check older fields my $Access1 = $Type1{"Fields"}{$Field_Name}{"Access"}; next if($Access1=~/private/); my $Field_Pos1 = $Type1{"Fields"}{$Field_Name}{"Pos"}; my $FieldType1_Id = $Type1{"Fields"}{$Field_Name}{"Type"}; my %FieldType1 = get_Type($FieldType1_Id, 1); if(not $Type2{"Fields"}{$Field_Name}) {# removed fields my $StraightPair_Name = findFieldPair($Field_Pos1, \%Type2); if($StraightPair_Name ne "lost" and not $Type1{"Fields"}{$StraightPair_Name} and $FieldType1{"Name"} eq get_TypeName($Type2{"Fields"}{$StraightPair_Name}{"Type"}, 2)) { if(my $Constant = get_ConstantValue($Type1{"Fields"}{$Field_Name}{"Value"}, $FieldType1{"Name"})) { %{$SubProblems{"Renamed_Constant_Field"}{$Field_Name}}=( "Target"=>$Field_Name, "Type_Name"=>$Type1{"Name"}, "Old_Value"=>$Field_Name, "New_Value"=>$StraightPair_Name, "Field_Type"=>$FieldType1{"Name"}, "Field_Value"=>$Constant ); } else { %{$SubProblems{"Renamed_Field"}{$Field_Name}}=( "Target"=>$Field_Name, "Type_Name"=>$Type1{"Name"}, "Old_Value"=>$Field_Name, "New_Value"=>$StraightPair_Name, "Field_Type"=>$FieldType1{"Name"} ); } } else { if(my $Constant = get_ConstantValue($Type1{"Fields"}{$Field_Name}{"Value"}, $FieldType1{"Name"})) { # has a compile-time constant value %{$SubProblems{"Removed_Constant_Field"}{$Field_Name}}=( "Target"=>$Field_Name, "Type_Name"=>$Type1{"Name"}, "Field_Value"=>$Constant, "Field_Type"=>$FieldType1{"Name"}, "Type_Type"=>$Type1{"Type"} ); } else { %{$SubProblems{"Removed_NonConstant_Field"}{$Field_Name}}=( "Target"=>$Field_Name, "Type_Name"=>$Type1{"Name"}, "Type_Type"=>$Type1{"Type"}, "Field_Type"=>$FieldType1{"Name"} ); } } next; } my $FieldType2_Id = $Type2{"Fields"}{$Field_Name}{"Type"}; my %FieldType2 = get_Type($FieldType2_Id, 2); if(not $Type1{"Fields"}{$Field_Name}{"Static"} and $Type2{"Fields"}{$Field_Name}{"Static"}) { if(not $Type1{"Fields"}{$Field_Name}{"Value"}) { %{$SubProblems{"NonConstant_Field_Became_Static"}{$Field_Name}}=( "Target"=>$Field_Name, "Field_Type"=>$FieldType1{"Name"}, "Type_Name"=>$Type1{"Name"} ); } } elsif($Type1{"Fields"}{$Field_Name}{"Static"} and not $Type2{"Fields"}{$Field_Name}{"Static"}) { if($Type1{"Fields"}{$Field_Name}{"Value"}) { %{$SubProblems{"Constant_Field_Became_NonStatic"}{$Field_Name}}=( "Target"=>$Field_Name, "Field_Type"=>$FieldType1{"Name"}, "Type_Name"=>$Type1{"Name"} ); } else { %{$SubProblems{"NonConstant_Field_Became_NonStatic"}{$Field_Name}}=( "Target"=>$Field_Name, "Field_Type"=>$FieldType1{"Name"}, "Type_Name"=>$Type1{"Name"} ); } } if(not $Type1{"Fields"}{$Field_Name}{"Final"} and $Type2{"Fields"}{$Field_Name}{"Final"}) { %{$SubProblems{"Field_Became_Final"}{$Field_Name}}=( "Target"=>$Field_Name, "Field_Type"=>$FieldType1{"Name"}, "Type_Name"=>$Type1{"Name"} ); } elsif($Type1{"Fields"}{$Field_Name}{"Final"} and not $Type2{"Fields"}{$Field_Name}{"Final"}) { %{$SubProblems{"Field_Became_NonFinal"}{$Field_Name}}=( "Target"=>$Field_Name, "Field_Type"=>$FieldType1{"Name"}, "Type_Name"=>$Type1{"Name"} ); } my $Access2 = $Type2{"Fields"}{$Field_Name}{"Access"}; if($Access1 eq "public" and $Access2=~/protected|private/ or $Access1 eq "protected" and $Access2=~/private/) { %{$SubProblems{"Changed_Field_Access"}{$Field_Name}}=( "Target"=>$Field_Name, "Type_Name"=>$Type1{"Name"}, "Old_Value"=>$Access1, "New_Value"=>$Access2 ); } my $Value1 = get_ConstantValue($Type1{"Fields"}{$Field_Name}{"Value"}, $FieldType1{"Name"}); my $Value2 = get_ConstantValue($Type2{"Fields"}{$Field_Name}{"Value"}, $FieldType2{"Name"}); if($Value1 ne $Value2) { if($Value1 and $Value2) { if($Type1{"Fields"}{$Field_Name}{"Final"} and $Type2{"Fields"}{$Field_Name}{"Final"}) { %{$SubProblems{"Changed_Final_Field_Value"}{$Field_Name}}=( "Target"=>$Field_Name, "Field_Type"=>$FieldType1{"Name"}, "Type_Name"=>$Type1{"Name"}, "Old_Value"=>$Value1, "New_Value"=>$Value2 ); } } } %Sub_SubProblems = detectTypeChange($FieldType1_Id, $FieldType2_Id, "Field"); foreach my $Sub_SubProblemType (keys(%Sub_SubProblems)) { %{$SubProblems{$Sub_SubProblemType}{$Field_Name}}=( "Target"=>$Field_Name, "Type_Name"=>$Type1{"Name"} ); foreach my $Attr (keys(%{$Sub_SubProblems{$Sub_SubProblemType}})) { $SubProblems{$Sub_SubProblemType}{$Field_Name}{$Attr} = $Sub_SubProblems{$Sub_SubProblemType}{$Attr}; } } if($FieldType1_Id and $FieldType2_Id) { # check field type change %Sub_SubProblems = mergeTypes($FieldType1_Id, $FieldType2_Id); foreach my $Sub_SubProblemType (keys(%Sub_SubProblems)) { foreach my $Sub_SubLocation (keys(%{$Sub_SubProblems{$Sub_SubProblemType}})) { my $NewLocation = ($Sub_SubLocation)?$Field_Name.".".$Sub_SubLocation:$Field_Name; foreach my $Attr (keys(%{$Sub_SubProblems{$Sub_SubProblemType}{$Sub_SubLocation}})) { $SubProblems{$Sub_SubProblemType}{$NewLocation}{$Attr} = $Sub_SubProblems{$Sub_SubProblemType}{$Sub_SubLocation}{$Attr}; } } } } } foreach my $Field_Name (sort keys(%{$Type2{"Fields"}})) { # check added fields next if($Type2{"Fields"}{$Field_Name}{"Access"}=~/private/); my $FieldPos2 = $Type2{"Fields"}{$Field_Name}{"Pos"}; my $FieldType2_Id = $Type2{"Fields"}{$Field_Name}{"Type"}; my %FieldType2 = get_Type($FieldType2_Id, 2); if(not $Type1{"Fields"}{$Field_Name}) {# added fields my $StraightPair_Name = findFieldPair($FieldPos2, \%Type1); if($StraightPair_Name ne "lost" and not $Type2{"Fields"}{$StraightPair_Name} and get_TypeName($Type1{"Fields"}{$StraightPair_Name}{"Type"}, 1) eq $FieldType2{"Name"}) { # Already reported as "Renamed_Field" or "Renamed_Constant_Field" } else { if($Type1{"Type"} eq "interface") { %{$SubProblems{"Interface_Added_Field"}{$Field_Name}}=( "Target"=>$Field_Name, "Type_Name"=>$Type1{"Name"} ); } else { %{$SubProblems{"Class_Added_Field"}{$Field_Name}}=( "Target"=>$Field_Name, "Type_Name"=>$Type1{"Name"} ); } } } } %{$Cache{"mergeTypes"}{$Type1_Id}{$Type2_Id}} = %SubProblems; pop(@RecurTypes); return %SubProblems; } sub unmangle($) { my $Name = $_[0]; $Name=~s!/!.!g; $Name=~s!:\(!(!g; $Name=~s!\).+\Z!)!g; if($Name=~/\A(.+)\((.+)\)/) { my ($ShortName, $MangledParams) = ($1, $2); my @UnmangledParams = (); my ($IsArray, $Shift, $Pos, $CurParam) = (0, 0, 0, ""); while($Pos".highLight_Signature($Name)."
"; } sub get_TypeName($$) { my ($TypeId, $LibVersion) = @_; return $TypeInfo{$LibVersion}{$TypeId}{"Name"}; } sub get_ShortName($$) { my ($TypeId, $LibVersion) = @_; my $TypeName = $TypeInfo{$LibVersion}{$TypeId}{"Name"}; $TypeName=~s/\A.*\.//g; return $TypeName; } sub get_TypeType($$) { my ($TypeId, $LibVersion) = @_; return $TypeInfo{$LibVersion}{$TypeId}{"Type"}; } sub get_TypeHeader($$) { my ($TypeId, $LibVersion) = @_; return $TypeInfo{$LibVersion}{$TypeId}{"Header"}; } sub get_BaseType($$) { my ($TypeId, $LibVersion) = @_; return "" if(not $TypeId); if(defined $Cache{"get_BaseType"}{$TypeId}{$LibVersion}) { return %{$Cache{"get_BaseType"}{$TypeId}{$LibVersion}}; } return "" if(not $TypeInfo{$LibVersion}{$TypeId}); my %Type = %{$TypeInfo{$LibVersion}{$TypeId}}; return %Type if(not $Type{"BaseType"}); %Type = get_BaseType($Type{"BaseType"}, $LibVersion); $Cache{"get_BaseType"}{$TypeId}{$LibVersion} = \%Type; return %Type; } sub get_OneStep_BaseType($$) { my ($TypeId, $LibVersion) = @_; return "" if(not $TypeId); return "" if(not $TypeInfo{$LibVersion}{$TypeId}); my %Type = %{$TypeInfo{$LibVersion}{$TypeId}}; return %Type if(not $Type{"BaseType"}); return get_Type($Type{"BaseType"}, $LibVersion); } sub get_Type($$) { my ($TypeId, $LibVersion) = @_; return "" if(not $TypeId); return "" if(not $TypeInfo{$LibVersion}{$TypeId}); return %{$TypeInfo{$LibVersion}{$TypeId}}; } sub methodFilter($$) { my ($Method, $LibVersion) = @_; my $ClassId = $MethodInfo{$LibVersion}{$Method}{"Class"}; my %Class = get_Type($ClassId, $LibVersion); my $Package = $MethodInfo{$LibVersion}{$Method}{"Package"}; if($ClassListPath and not $ClassList_User{$Class{"Name"}}) { # user defined classes return 0; } if($SkipClasses and $SkipClass_User{$Class{"Name"}}) { # user defined classes return 0; } if($ClientPath and not $UsedMethods_Client{$Method}) { # user defined application return 0; } if(skip_package($Package, $LibVersion)) { # internal packages return 0; } if(defined $SkipDeprecated) { if($Class{"Deprecated"}) { # deprecated class return 0; } if($MethodInfo{$LibVersion}{$Method}{"Deprecated"}) { # deprecated method return 0; } } return 1; } sub skip_package($$) { my ($Package, $LibVersion) = @_; return 0 if(not $Package); if(not $KeepInternal) { if($Package=~/\A(com\.oracle|com\.sun|COM\.rsa|sun|sunw)/) { # private packages # http://java.sun.com/products/jdk/faq/faq-sun-packages.html return 1; } if($Package=~/(\A|\.)(internal|impl|examples)(\.|\Z)/) { # internal packages return 1; } } foreach my $SkipPackage (keys(%{$SkipPackages{$LibVersion}})) { if($Package=~/(\A|\.)\Q$SkipPackage\E(\.|\Z)/) { # user skipped packages return 1; } } if(my @Keeped = keys(%{$KeepPackages{$LibVersion}})) { my $UserKeeped = 0; foreach my $KeepPackage (@Keeped) { if($Package=~/(\A|\.)\Q$KeepPackage\E(\.|\Z)/) { # user keeped packages $UserKeeped = 1; } } if(not $UserKeeped) { return 1; } } return 0; } sub mergeImplementations() { my $DiffCmd = get_CmdPath("diff"); if(not $DiffCmd) { exitStatus("Not_Found", "can't find \"diff\" command"); } foreach my $Method (sort keys(%{$MethodInfo{1}})) { # implementation changes next if($MethodInfo{1}{$Method}{"Access"}=~/private/); next if(not defined $MethodInfo{2}{$Method}); next if(not methodFilter($Method, 1)); my $Impl1 = canonifyCode($MethodBody{1}{$Method}); next if(not $Impl1); my $Impl2 = canonifyCode($MethodBody{2}{$Method}); next if(not $Impl2); if($Impl1 ne $Impl2) { writeFile("$TMP_DIR/impl1", $Impl1); writeFile("$TMP_DIR/impl2", $Impl2); my $Diff = `$DiffCmd -rNau \"$TMP_DIR/impl1\" \"$TMP_DIR/impl2\"`; $Diff=~s/(---|\+\+\+).+\n//g; $Diff=~s/\n\@\@/\n \n\@\@/g; unlink("$TMP_DIR/impl1"); unlink("$TMP_DIR/impl2"); %{$ImplProblems{$Method}}=( "Diff" => get_CodeView($Diff) ); } } } sub canonifyCode($) { my $MethodBody = $_[0]; return "" if(not $MethodBody); $MethodBody=~s/#\d+; //g; return $MethodBody; } sub get_CodeView($) { my $Code = $_[0]; my $View = ""; foreach my $Line (split(/\n/, $Code)) { if($Line=~s/\A(\+|-)/$1 /g) { $Line = "".htmlSpecChars($Line).""; } else { $Line = "".htmlSpecChars($Line).""; } $View .= "$Line\n"; } return "$View
\n"; } sub get_MSuffix($) { my $Method = $_[0]; if($Method=~/(\(.*\))/) { return $1; } return ""; } sub get_MShort($) { my $Method = $_[0]; if($Method=~/([^\.]+)\:\(/) { return $1; } return ""; } sub findMethod($$$$) { my ($Method, $MethodVersion, $ClassName, $ClassVersion) = @_; my $ClassId = $TName_Tid{$ClassVersion}{$ClassName}; if(not $ClassId) { return ""; } my @Search = (); if(get_TypeType($ClassId, $ClassVersion) eq "class") { if(my $SuperClassId = $TypeInfo{$ClassVersion}{$ClassId}{"SuperClass"}) { push(@Search, $SuperClassId); } } if(not defined $MethodInfo{$MethodVersion}{$Method} or $MethodInfo{$MethodVersion}{$Method}{"Abstract"}) { if(my @SuperInterfaces = keys(%{$TypeInfo{$ClassVersion}{$ClassId}{"SuperInterface"}})) { push(@Search, @SuperInterfaces); } } foreach my $SuperId (@Search) { my $SuperName = get_TypeName($SuperId, $ClassVersion); if(my $MethodInClass = findMethod_Class($Method, $SuperName, $ClassVersion)) { return $MethodInClass; } elsif(my $MethodInSuperClasses = findMethod($Method, $MethodVersion, $SuperName, $ClassVersion)) { return $MethodInSuperClasses; } } return ""; } sub findMethod_Class($$$) { my ($Method, $ClassName, $ClassVersion) = @_; my $TargetSuffix = get_MSuffix($Method); my $TargetShortName = get_MShort($Method); foreach my $Candidate (keys(%{$Class_Methods{$ClassVersion}{$ClassName}})) {# search for method with the same parameters suffix next if($MethodInfo{$ClassVersion}{$Candidate}{"Constructor"}); if($TargetSuffix eq get_MSuffix($Candidate)) { if($TargetShortName eq get_MShort($Candidate)) { return $Candidate; } } } return ""; } sub prepareMethods($) { my $LibVersion = $_[0]; foreach my $Method (keys(%{$MethodInfo{$LibVersion}})) { if($MethodInfo{$LibVersion}{$Method}{"Access"}!~/private/) { if($MethodInfo{$LibVersion}{$Method}{"Constructor"}) { registerUsage($MethodInfo{$LibVersion}{$Method}{"Class"}, $LibVersion); } else { registerUsage($MethodInfo{$LibVersion}{$Method}{"Return"}, $LibVersion); } } } } sub mergeMethods() { my %SubProblems = (); foreach my $Method (sort keys(%{$MethodInfo{1}})) { # compare methods next if(not defined $MethodInfo{2}{$Method}); if(not $MethodInfo{1}{$Method}{"Archive"} or not $MethodInfo{2}{$Method}{"Archive"}) { next; } if($MethodInfo{1}{$Method}{"Access"}=~/private/) { # skip private methods next; } next if(not methodFilter($Method, 1)); $CheckedMethods{$Method}=1; my $ClassId1 = $MethodInfo{1}{$Method}{"Class"}; my %Class1 = get_Type($ClassId1, 1); if($Class1{"Access"}=~/private/) {# skip private classes next; } my %Class2 = get_Type($MethodInfo{2}{$Method}{"Class"}, 2); if(not $MethodInfo{1}{$Method}{"Static"} and $Class1{"Type"} eq "class" and not $Class_Constructed{1}{$ClassId1}) { # class cannot be constructed or inherited by clients # non-static method cannot be called next; } # checking attributes if(not $MethodInfo{1}{$Method}{"Static"} and $MethodInfo{2}{$Method}{"Static"}) { %{$CompatProblems{$Method}{"Method_Became_Static"}{""}} = (); } elsif($MethodInfo{1}{$Method}{"Static"} and not $MethodInfo{2}{$Method}{"Static"}) { %{$CompatProblems{$Method}{"Method_Became_NonStatic"}{""}} = (); } if(not $MethodInfo{1}{$Method}{"Synchronized"} and $MethodInfo{2}{$Method}{"Synchronized"}) { %{$CompatProblems{$Method}{"Method_Became_Synchronized"}{""}} = (); } elsif($MethodInfo{1}{$Method}{"Synchronized"} and not $MethodInfo{2}{$Method}{"Synchronized"}) { %{$CompatProblems{$Method}{"Method_Became_NonSynchronized"}{""}} = (); } if(not $MethodInfo{1}{$Method}{"Final"} and $MethodInfo{2}{$Method}{"Final"}) { if($MethodInfo{1}{$Method}{"Static"}) { %{$CompatProblems{$Method}{"Static_Method_Became_Final"}{""}} = (); } else { %{$CompatProblems{$Method}{"NonStatic_Method_Became_Final"}{""}} = (); } } my $Access1 = $MethodInfo{1}{$Method}{"Access"}; my $Access2 = $MethodInfo{2}{$Method}{"Access"}; if($Access1 eq "public" and $Access2=~/protected|private/ or $Access1 eq "protected" and $Access2=~/private/) { %{$CompatProblems{$Method}{"Changed_Method_Access"}{""}} = ( "Old_Value"=>$Access1, "New_Value"=>$Access2 ); } if($Class1{"Type"} eq "class" and $Class2{"Type"} eq "class") { if(not $MethodInfo{1}{$Method}{"Abstract"} and $MethodInfo{2}{$Method}{"Abstract"}) { %{$CompatProblems{$Method}{"Method_Became_Abstract"}{""}} = (); %{$CompatProblems{$Method}{"Class_Method_Became_Abstract"}{"this.".get_SFormat($Method)}} = ( "Type_Name"=>$Class1{"Name"}, "Target"=>$Method ); } elsif($MethodInfo{1}{$Method}{"Abstract"} and not $MethodInfo{2}{$Method}{"Abstract"}) { %{$CompatProblems{$Method}{"Method_Became_NonAbstract"}{""}} = (); %{$CompatProblems{$Method}{"Class_Method_Became_NonAbstract"}{"this.".get_SFormat($Method)}} = ( "Type_Name"=>$Class1{"Name"}, "Target"=>$Method ); } } my %Exceptions_Old = map {get_TypeName($_, 1) => $_} keys(%{$MethodInfo{1}{$Method}{"Exceptions"}}); my %Exceptions_New = map {get_TypeName($_, 2) => $_} keys(%{$MethodInfo{2}{$Method}{"Exceptions"}}); foreach my $Exception (keys(%Exceptions_Old)) { if(not $Exceptions_New{$Exception}) { my %ExceptionType = get_Type($Exceptions_Old{$Exception}, 1); my $SuperClass = $ExceptionType{"SuperClass"}; if($KnownRuntimeExceptions{$Exception} or defined $SuperClass and get_TypeName($SuperClass, 1) eq "java.lang.RuntimeException") { if(not $MethodInfo{1}{$Method}{"Abstract"} and not $MethodInfo{2}{$Method}{"Abstract"}) { %{$CompatProblems{$Method}{"Removed_Unchecked_Exception"}{"this.".get_SFormat($Exception)}} = ( "Type_Name"=>$Class1{"Name"}, "Target"=>$Exception ); } } else { if($MethodInfo{1}{$Method}{"Abstract"} and $MethodInfo{2}{$Method}{"Abstract"}) { %{$CompatProblems{$Method}{"Abstract_Method_Removed_Checked_Exception"}{"this.".get_SFormat($Exception)}} = ( "Type_Name"=>$Class1{"Name"}, "Target"=>$Exception ); } else { %{$CompatProblems{$Method}{"NonAbstract_Method_Removed_Checked_Exception"}{"this.".get_SFormat($Exception)}} = ( "Type_Name"=>$Class1{"Name"}, "Target"=>$Exception ); } } } } foreach my $Exception (keys(%Exceptions_New)) { if(not $Exceptions_Old{$Exception}) { my %ExceptionType = get_Type($Exceptions_New{$Exception}, 2); my $SuperClass = $ExceptionType{"SuperClass"}; if($KnownRuntimeExceptions{$Exception} or defined $SuperClass and get_TypeName($SuperClass, 2) eq "java.lang.RuntimeException") { if(not $MethodInfo{1}{$Method}{"Abstract"} and not $MethodInfo{2}{$Method}{"Abstract"}) { %{$CompatProblems{$Method}{"Added_Unchecked_Exception"}{"this.".get_SFormat($Exception)}} = ( "Type_Name"=>$Class1{"Name"}, "Target"=>$Exception ); } } else { if($MethodInfo{1}{$Method}{"Abstract"} and $MethodInfo{2}{$Method}{"Abstract"}) { %{$CompatProblems{$Method}{"Abstract_Method_Added_Checked_Exception"}{"this.".get_SFormat($Exception)}} = ( "Type_Name"=>$Class1{"Name"}, "Target"=>$Exception ); } else { %{$CompatProblems{$Method}{"NonAbstract_Method_Added_Checked_Exception"}{"this.".get_SFormat($Exception)}} = ( "Type_Name"=>$Class1{"Name"}, "Target"=>$Exception ); } } } } foreach my $ParamPos (sort {int($a) <=> int($b)} keys(%{$MethodInfo{1}{$Method}{"Param"}})) { # checking parameters mergeParameters($Method, $ParamPos, $ParamPos); } # check object type my $ObjectType1_Id = $MethodInfo{1}{$Method}{"Class"}; my $ObjectType2_Id = $MethodInfo{2}{$Method}{"Class"}; if($ObjectType1_Id and $ObjectType2_Id) { @RecurTypes = (); %SubProblems = mergeTypes($ObjectType1_Id, $ObjectType2_Id); foreach my $SubProblemType (keys(%SubProblems)) { foreach my $SubLocation (keys(%{$SubProblems{$SubProblemType}})) { my $NewLocation = ($SubLocation)?"this.".$SubLocation:"this"; @{$CompatProblems{$Method}{$SubProblemType}{$NewLocation}}{keys(%{$SubProblems{$SubProblemType}{$SubLocation}})} = values %{$SubProblems{$SubProblemType}{$SubLocation}}; } } } # check return type my $ReturnType1_Id = $MethodInfo{1}{$Method}{"Return"}; my $ReturnType2_Id = $MethodInfo{2}{$Method}{"Return"}; if($ReturnType1_Id and $ReturnType2_Id) { @RecurTypes = (); %SubProblems = mergeTypes($ReturnType1_Id, $ReturnType2_Id); foreach my $SubProblemType (keys(%SubProblems)) { foreach my $SubLocation (keys(%{$SubProblems{$SubProblemType}})) { my $NewLocation = ($SubLocation)?"retval.".$SubLocation:"retval"; @{$CompatProblems{$Method}{$SubProblemType}{$NewLocation}}{keys(%{$SubProblems{$SubProblemType}{$SubLocation}})} = values %{$SubProblems{$SubProblemType}{$SubLocation}}; } } } } } sub mergeParameters($$$) { my ($Method, $ParamPos1, $ParamPos2) = @_; return if(not $Method or not defined $MethodInfo{1}{$Method}{"Param"} or not defined $MethodInfo{2}{$Method}{"Param"}); my $ParamType1_Id = $MethodInfo{1}{$Method}{"Param"}{$ParamPos1}{"Type"}; my $Parameter_Name = $MethodInfo{1}{$Method}{"Param"}{$ParamPos1}{"Name"}; my $ParamType2_Id = $MethodInfo{2}{$Method}{"Param"}{$ParamPos2}{"Type"}; return if(not $ParamType1_Id or not $ParamType2_Id); my $Parameter_Location = ($Parameter_Name)?$Parameter_Name:showPos($ParamPos1)." Parameter"; # checking type declaration changes my %SubProblems = mergeTypes($ParamType1_Id, $ParamType2_Id); foreach my $SubProblemType (keys(%SubProblems)) { foreach my $SubLocation (keys(%{$SubProblems{$SubProblemType}})) { my $NewLocation = ($SubLocation)?$Parameter_Location.".".$SubLocation:$Parameter_Location; %{$CompatProblems{$Method}{$SubProblemType}{$NewLocation}}=( "Parameter_Type_Name"=>get_TypeName($ParamType1_Id, 1), "Parameter_Position"=>$ParamPos1, "Parameter_Name"=>$Parameter_Name); @{$CompatProblems{$Method}{$SubProblemType}{$NewLocation}}{keys(%{$SubProblems{$SubProblemType}{$SubLocation}})} = values %{$SubProblems{$SubProblemType}{$SubLocation}}; } } } sub detectTypeChange($$$) { my ($Type1_Id, $Type2_Id, $Prefix) = @_; my %LocalProblems = (); my %Type1 = get_Type($Type1_Id, 1); my %Type2 = get_Type($Type2_Id, 2); my %Type1_Base = ($Type1{"Type"} eq "array")?get_OneStep_BaseType($Type1_Id, 1):get_BaseType($Type1_Id, 1); my %Type2_Base = ($Type2{"Type"} eq "array")?get_OneStep_BaseType($Type2_Id, 2):get_BaseType($Type2_Id, 2); return () if(not $Type1{"Name"} or not $Type2{"Name"}); return () if(not $Type1_Base{"Name"} or not $Type2_Base{"Name"}); if($Type1_Base{"Name"} ne $Type2_Base{"Name"} and $Type1{"Name"} eq $Type2{"Name"}) {# base type change %{$LocalProblems{"Changed_".$Prefix."_BaseType"}}=( "Old_Value"=>$Type1_Base{"Name"}, "New_Value"=>$Type2_Base{"Name"} ); } elsif($Type1{"Name"} ne $Type2{"Name"}) {# type change %{$LocalProblems{"Changed_".$Prefix."_Type"}}=( "Old_Value"=>$Type1{"Name"}, "New_Value"=>$Type2{"Name"} ); } return %LocalProblems; } sub htmlSpecChars($) { my $Str = $_[0]; if(not defined $Str or $Str eq "") { return ""; } $Str=~s/\&([^#]|\Z)/&$1/g; $Str=~s//->/g; # − $Str=~s/>/>/g; $Str=~s/([^ ])( )([^ ])/$1\@ALONE_SP\@$3/g; $Str=~s/ / /g; #   $Str=~s/\@ALONE_SP\@/ /g; $Str=~s/\n//g; $Str=~s/\"/"/g; $Str=~s/\'/'/g; return $Str; } sub highLight_Signature($) { return highLight_Signature_PPos_Italic($_[0], "", 1, 0); } sub highLight_Signature_Italic($) { return highLight_Signature_PPos_Italic($_[0], "", 1, 0); } sub highLight_Signature_Italic_Color($) { return highLight_Signature_PPos_Italic($_[0], "", 1, 1); } sub highLight_Signature_PPos_Italic($$$$) { my ($Signature, $Param_Pos, $ItalicParams, $ColorParams) = @_; $Param_Pos = "" if(not defined $Param_Pos); if($Signature!~/\)/) { # global data $Signature = htmlSpecChars($Signature); $Signature =~ s!(\[data\])!$1!g; return $Signature; } my ($Begin, $End, $Return) = ("", "", ""); if($Signature=~s/\s+:(.+)//g) { $Return = $1; } if($Signature=~/(.+)\(.*\)((| \[static\]| \[abstract\])(| \[(public|private|protected)\]))\Z/) { ($Begin, $End) = ($1, $2); } $Begin.=" " if($Begin!~/ \Z/); my @Parts = (); my @SParts = get_s_params($Signature, 1); foreach my $Pos (0 .. $#SParts) { my $Part = $SParts[$Pos]; $Part=~s/\A\s+|\s+\Z//g; my ($Part_Styled, $ParamName) = (htmlSpecChars($Part), ""); if($Part=~/(\w+)[\,\)]*\Z/i) { $ParamName = $1; } if(not $ParamName) { push(@Parts, $Part_Styled); next; } if($ItalicParams and not $TName_Tid{1}{$Part} and not $TName_Tid{2}{$Part}) { my $Style = "param"; if($Param_Pos ne "" and $Pos==$Param_Pos) { $Style = "focus_p"; } elsif($ColorParams) { $Style = "color_p"; } $Part_Styled =~ s!(\W)$ParamName([\,\)]|\Z)!$1$ParamName$2!ig; } push(@Parts, $Part_Styled); } if(@Parts) { foreach my $Num (0 .. $#Parts) { if($Num==$#Parts) { # add ")" to the last parameter $Parts[$Num] = "".$Parts[$Num]." )"; } elsif(length($Parts[$Num])<=45) { $Parts[$Num] = "".$Parts[$Num].""; } } $Signature = htmlSpecChars($Begin)."( ".join(" ", @Parts).""; } else { $Signature = htmlSpecChars($Begin)."( )"; } if($End and $ColorParams) { $Signature .= $End; } if($Return and $ColorParams) { $Signature .= "  :  ".htmlSpecChars($Return).""; } $Signature=~s!\[\]![ ]!g; $Signature=~s!operator=!operator =!g; $Signature=~s!(\[static\]|\[abstract\])!$1!g; $Signature=~s!(\[public\]|\[private\]|\[protected\])!$1!g; return $Signature; } sub get_s_params($$) { my ($Signature, $Comma) = @_; if($Signature=~/\((.+)\)/) { my @Params = split(/,\s*/, $1); if($Comma) { foreach (0 .. $#Params) { if($_!=$#Params) { $Params[$_].=","; } } } return @Params; } return (); } sub checkJavaCompiler($) {# check javac: compile simple program my $Cmd = $_[0]; return if(not $Cmd); writeFile($TMP_DIR."/test_javac/Simple.java", "public class Simple { public Integer f; public void method(Integer p) { }; }"); chdir($TMP_DIR."/test_javac"); system("$Cmd Simple.java 2>errors.txt"); chdir($ORIG_DIR); if($?) { my $Msg = "something is going wrong with the Java compiler (javac):\n"; my $Err = readFile($TMP_DIR."/test_javac/errors.txt"); $Msg .= $Err; if($Err=~/elf\/start\.S/ and $Err=~/undefined\s+reference\s+to/) { # /usr/lib/gcc/i586-suse-linux/4.5/../../../crt1.o: In function _start: # /usr/src/packages/BUILD/glibc-2.11.3/csu/../sysdeps/i386/elf/start.S:115: undefined reference to main $Msg .= "\nDid you install a JDK-devel package?"; } exitStatus("Error", $Msg); } } sub runTests($$$$) { my ($TestsPath, $PackageName, $Path_v1, $Path_v2) = @_; # compile with old version of package my $JavacCmd = get_CmdPath("javac"); if(not $JavacCmd) { exitStatus("Not_Found", "can't find \"javac\" compiler"); } my $JavaCmd = get_CmdPath("java"); if(not $JavaCmd) { exitStatus("Not_Found", "can't find \"java\" command"); } mkpath($TestsPath."/$PackageName/"); foreach my $ClassPath (cmd_find($Path_v1,"","*\.class","")) {# create a compile-time package copy copy($ClassPath, $TestsPath."/$PackageName/"); } chdir($TestsPath); system($JavacCmd." -g *.java"); chdir($ORIG_DIR); foreach my $TestSrc (cmd_find($TestsPath,"","*\.java","")) { # remove test source unlink($TestSrc); } rmtree($TestsPath."/$PackageName"); mkpath($TestsPath."/$PackageName/"); foreach my $ClassPath (cmd_find($Path_v2,"","*\.class","")) {# create a run-time package copy copy($ClassPath, $TestsPath."/$PackageName/"); } my $TEST_REPORT = ""; foreach my $TestPath (cmd_find($TestsPath,"","*\.class",1)) {# run tests my $Name = get_filename($TestPath); $Name=~s/\.class\Z//g; chdir($TestsPath); system($JavaCmd." $Name >result.txt 2>&1"); chdir($ORIG_DIR); my $Result = readFile($TestsPath."/result.txt"); unlink($TestsPath."/result.txt"); $TEST_REPORT .= "TEST CASE: $Name\n"; if($Result) { $TEST_REPORT .= "RESULT: FAILED\n"; $TEST_REPORT .= "OUTPUT:\n$Result\n"; } else { $TEST_REPORT .= "RESULT: SUCCESS\n"; } $TEST_REPORT .= "\n"; } writeFile("$TestsPath/Journal.txt", $TEST_REPORT); rmtree($TestsPath."/$PackageName"); } sub compileJavaLib($$$) { my ($LibName, $BuildRoot1, $BuildRoot2) = @_; my $JavacCmd = get_CmdPath("javac"); if(not $JavacCmd) { exitStatus("Not_Found", "can't find \"javac\" compiler"); } checkJavaCompiler($JavacCmd); my $JarCmd = get_CmdPath("jar"); if(not $JarCmd) { exitStatus("Not_Found", "can't find \"jar\" command"); } writeFile("$BuildRoot1/MANIFEST.MF", "Implementation-Version: 1.0\n"); # space before value, new line writeFile("$BuildRoot2/MANIFEST.MF", "Implementation-Version: 2.0\n"); my (%SrcDir1, %SrcDir2) = (); foreach my $Path (cmd_find($BuildRoot1,"f","*.java","")) { $SrcDir1{get_dirname($Path)} = 1; } foreach my $Path (cmd_find($BuildRoot2,"f","*.java","")) { $SrcDir2{get_dirname($Path)} = 1; } # build classes v.1 foreach my $Dir (keys(%SrcDir1)) { chdir($Dir); system("$JavacCmd -g *.java"); chdir($ORIG_DIR); if($?) { exitStatus("Error", "can't compile classes v.1"); } } # create java archive v.1 chdir($BuildRoot1); system("$JarCmd -cmf MANIFEST.MF $LibName.jar TestPackage"); chdir($ORIG_DIR); # build classes v.2 foreach my $Dir (keys(%SrcDir2)) { chdir($Dir); system("$JavacCmd -g *.java"); chdir($ORIG_DIR); if($?) { exitStatus("Error", "can't compile classes v.2"); } } # create java archive v.2 chdir($BuildRoot2); system("$JarCmd -cmf MANIFEST.MF $LibName.jar TestPackage"); chdir($ORIG_DIR); foreach my $SrcPath (cmd_find($BuildRoot1,"","*\.java","")) { unlink($SrcPath); } foreach my $SrcPath (cmd_find($BuildRoot2,"","*\.java","")) { unlink($SrcPath); } return 1; } sub readLineNum($$) { my ($Path, $Num) = @_; return "" if(not $Path or not -f $Path); open (FILE, $Path); foreach (1 ... $Num) { ; } my $Line = ; close(FILE); return $Line; } sub readAttributes($$) { my ($Path, $Num) = @_; return () if(not $Path or not -f $Path); my %Attributes = (); if(readLineNum($Path, $Num)=~//) { foreach my $AttrVal (split(/;/, $1)) { if($AttrVal=~/(.+):(.+)/) { my ($Name, $Value) = ($1, $2); $Attributes{$Name} = $Value; } } } return \%Attributes; } sub runChecker($$$) { my ($LibName, $Path1, $Path2) = @_; writeFile("$LibName/v1.xml", " 1.0 ".get_abs_path($Path1)." "); writeFile("$LibName/v2.xml", " 2.0 ".get_abs_path($Path2)." "); my $Cmd = "perl $0 -l $LibName $LibName/v1.xml $LibName/v2.xml"; if($OSgroup ne "windows") { $Cmd .= " -check-impl"; } if($Browse) { $Cmd .= " -b $Browse"; } if($OpenReport) { $Cmd .= " -open"; } if($Quick) { $Cmd .= " -quick"; } if(defined $SkipDeprecated) { $Cmd .= " -skip-deprecated"; } if($Debug) { $Cmd .= " -debug"; printMsg("INFO", "running $Cmd"); } system($Cmd); my $Report = "compat_reports/$LibName/1.0_to_2.0/compat_report.html"; # Binary my $BReport = readAttributes($Report, 0); my $NProblems = $BReport->{"type_problems_high"}+$BReport->{"type_problems_medium"}; $NProblems += $BReport->{"method_problems_high"}+$BReport->{"method_problems_medium"}; $NProblems += $BReport->{"removed"}; # Source my $SReport = readAttributes($Report, 1); $NProblems += $SReport->{"type_problems_high"}+$SReport->{"type_problems_medium"}; $NProblems += $SReport->{"method_problems_high"}+$SReport->{"method_problems_medium"}; $NProblems += $SReport->{"removed"}; if($NProblems>=100) { printMsg("INFO", "test result: SUCCESS ($NProblems breaks found)\n"); } else { printMsg("ERROR", "test result: FAILED ($NProblems breaks found)\n"); } } sub writeFile($$) { my ($Path, $Content) = @_; return if(not $Path); if(my $Dir = get_dirname($Path)) { mkpath($Dir); } open (FILE, ">".$Path) || die ("can't open file \'$Path\': $!\n"); print FILE $Content; close(FILE); } sub readFile($) { my $Path = $_[0]; return "" if(not $Path or not -f $Path); open (FILE, $Path); my $Content = join("", ); close(FILE); $Content=~s/\r//g; return $Content; } sub appendFile($$) { my ($Path, $Content) = @_; return if(not $Path); if(my $Dir = get_dirname($Path)) { mkpath($Dir); } open(FILE, ">>".$Path) || die ("can't open file \'$Path\': $!\n"); print FILE $Content; close(FILE); } sub get_Report_Header($) { my $Level = $_[0]; my $Report_Header = "

"; if($Level eq "Source") { $Report_Header .= "Source compatibility"; } elsif($Level eq "Binary") { $Report_Header .= "Binary compatibility"; } else { $Report_Header .= "API compatibility"; } $Report_Header .= " report for the $TargetLibraryFullName library  between ".$Descriptor{1}{"Version"}." and ".$Descriptor{2}{"Version"}." versions"; if($ClientPath) { $Report_Header .= "   (relating to the portability of client application ".get_filename($ClientPath).")"; } $Report_Header .= "

\n"; return $Report_Header; } sub get_SourceInfo() { my $CheckedArchives = "

Java ARchives (".keys(%{$LibArchives{1}}).")

\n"; $CheckedArchives .= "
\n"; foreach my $ArchivePath (sort {lc($a) cmp lc($b)} keys(%{$LibArchives{1}})) { $CheckedArchives .= get_filename($ArchivePath)."
\n"; } $CheckedArchives .= "

$TOP_REF
\n"; return $CheckedArchives; } sub get_TypeProblems_Count($$$) { my ($TypeChanges, $TargetSeverity, $Level) = @_; my $Type_Problems_Count = 0; foreach my $Type_Name (sort keys(%{$TypeChanges})) { my %Kinds_Target = (); foreach my $Kind (sort keys(%{$TypeChanges->{$Type_Name}})) { foreach my $Location (sort keys(%{$TypeChanges->{$Type_Name}{$Kind}})) { my $Target = $TypeChanges->{$Type_Name}{$Kind}{$Location}{"Target"}; my $Priority = getProblemSeverity($Level, $Kind, $Type_Name, $Target); next if($Priority ne $TargetSeverity); if($Kinds_Target{$Kind}{$Target}) { next; } if(cmpSeverities($Type_MaxPriority{$Level}{$Type_Name}{$Kind}{$Target}, $Priority)) { # select a problem with the highest priority next; } $Kinds_Target{$Kind}{$Target} = 1; $Type_Problems_Count += 1; } } } return $Type_Problems_Count; } sub show_number($) { if($_[0]) { my $Num = cut_off_number($_[0], 2, 0); if($Num eq "0") { foreach my $P (3 .. 7) { $Num = cut_off_number($_[0], $P, 1); if($Num ne "0") { last; } } } if($Num eq "0") { $Num = $_[0]; } return $Num; } return $_[0]; } sub cut_off_number($$$) { my ($num, $digs_to_cut, $z) = @_; if($num!~/\./) { $num .= "."; foreach (1 .. $digs_to_cut-1) { $num .= "0"; } } elsif($num=~/\.(.+)\Z/ and length($1)<$digs_to_cut-1) { foreach (1 .. $digs_to_cut - 1 - length($1)) { $num .= "0"; } } elsif($num=~/\d+\.(\d){$digs_to_cut,}/) { $num=sprintf("%.".($digs_to_cut-1)."f", $num); } $num=~s/\.[0]+\Z//g; if($z) { $num=~s/(\.[1-9]+)[0]+\Z/$1/g; } return $num; } sub get_Summary($) { my $Level = $_[0]; my ($Added, $Removed, $M_Problems_High, $M_Problems_Medium, $M_Problems_Low, $T_Problems_High, $T_Problems_Medium, $T_Problems_Low, $M_Other, $T_Other) = (0,0,0,0,0,0,0,0,0,0); %{$RESULT{$Level}} = ( "Problems"=>0, "Warnings"=>0, "Affected"=>0 ); foreach my $Method (sort keys(%CompatProblems)) { foreach my $Kind (sort keys(%{$CompatProblems{$Method}})) { if($MethodProblems_Kind{$Level}{$Kind}) { foreach my $Location (sort keys(%{$CompatProblems{$Method}{$Kind}})) { my $Type_Name = $CompatProblems{$Method}{$Kind}{$Location}{"Type_Name"}; my $Target = $CompatProblems{$Method}{$Kind}{$Location}{"Target"}; my $Priority = getProblemSeverity($Level, $Kind, $Type_Name, $Target); if($Kind eq "Added_Method") { if($Level eq "Source") { if($ChangedReturnFromVoid{$Method}) { next; } } $Added+=1; } elsif($Kind eq "Removed_Method") { if($Level eq "Source") { if($ChangedReturnFromVoid{$Method}) { next; } } $Removed+=1; $TotalAffected{$Level}{$Method} = $Priority; } else { if($Priority eq "Safe") { $M_Other += 1; } elsif($Priority eq "High") { $M_Problems_High+=1; } elsif($Priority eq "Medium") { $M_Problems_Medium+=1; } elsif($Priority eq "Low") { $M_Problems_Low+=1; } if(($Priority ne "Low" or $StrictCompat) and $Priority ne "Safe") { $TotalAffected{$Level}{$Method} = $Priority; } } } } } } my %TypeChanges = (); foreach my $Method (sort keys(%CompatProblems)) { foreach my $Kind (sort keys(%{$CompatProblems{$Method}})) { if($TypeProblems_Kind{$Level}{$Kind}) { foreach my $Location (sort keys(%{$CompatProblems{$Method}{$Kind}})) { my $Type_Name = $CompatProblems{$Method}{$Kind}{$Location}{"Type_Name"}; my $Target = $CompatProblems{$Method}{$Kind}{$Location}{"Target"}; my $Priority = getProblemSeverity($Level, $Kind, $Type_Name, $Target); if(cmpSeverities($Type_MaxPriority{$Level}{$Type_Name}{$Kind}{$Target}, $Priority)) { # select a problem with the highest priority next; } if(($Priority ne "Low" or $StrictCompat) and $Priority ne "Safe") { $TotalAffected{$Level}{$Method} = maxSeverity($TotalAffected{$Level}{$Method}, $Priority); } %{$TypeChanges{$Type_Name}{$Kind}{$Location}} = %{$CompatProblems{$Method}{$Kind}{$Location}}; $Type_MaxPriority{$Level}{$Type_Name}{$Kind}{$Target} = maxSeverity($Type_MaxPriority{$Level}{$Type_Name}{$Kind}{$Target}, $Priority); } } } } $T_Problems_High = get_TypeProblems_Count(\%TypeChanges, "High", $Level); $T_Problems_Medium = get_TypeProblems_Count(\%TypeChanges, "Medium", $Level); $T_Problems_Low = get_TypeProblems_Count(\%TypeChanges, "Low", $Level); $T_Other = get_TypeProblems_Count(\%TypeChanges, "Safe", $Level); # changed and removed public symbols my $SCount = keys(%CheckedMethods); if($SCount) { my %Weight = ( "High" => 100, "Medium" => 50, "Low" => 25 ); foreach (keys(%{$TotalAffected{$Level}})) { $RESULT{$Level}{"Affected"}+=$Weight{$TotalAffected{$Level}{$_}}; } $RESULT{$Level}{"Affected"} = $RESULT{$Level}{"Affected"}/$SCount; } else { $RESULT{$Level}{"Affected"} = 0; } $RESULT{$Level}{"Affected"} = show_number($RESULT{$Level}{"Affected"}); if($RESULT{$Level}{"Affected"}>=100) { $RESULT{$Level}{"Affected"} = 100; } my ($TestInfo, $TestResults, $Problem_Summary) = (); # test info $TestInfo .= "

Test Info


\n"; $TestInfo .= "\n"; $TestInfo .= "\n"; $TestInfo .= "\n"; $TestInfo .= "\n"; if($JoinReport) { if($Level eq "Binary") { $TestInfo .= "\n"; # Run-time } if($Level eq "Source") { $TestInfo .= "\n"; # Build-time } } $TestInfo .= "
Library Name$TargetLibraryFullName
Version #1".$Descriptor{1}{"Version"}."
Version #2".$Descriptor{2}{"Version"}."
SubjectBinary Compatibility
SubjectSource Compatibility
\n"; # test results $TestResults .= "

Test Results


"; $TestResults .= ""; my $Checked_Archives_Link = "0"; $Checked_Archives_Link = "".keys(%{$LibArchives{1}})."" if(keys(%{$LibArchives{1}})>0); $TestResults .= ""; $TestResults .= ""; # keys(%CheckedTypes) my $Verdict = ""; $RESULT{$Level}{"Problems"} += $Removed+$M_Problems_High+$T_Problems_High+$T_Problems_Medium+$M_Problems_Medium; if($StrictCompat) { $RESULT{$Level}{"Problems"}+=$T_Problems_Low+$M_Problems_Low; } else { $RESULT{$Level}{"Warnings"}+=$T_Problems_Low+$M_Problems_Low; } if($RESULT{$Level}{"Problems"}) { $Verdict = "Incompatible"; } else { $Verdict = "Compatible"; } my $META_DATA = "kind:".lc($Level).";"; $META_DATA .= $RESULT{$Level}{"Problems"}?"verdict:incompatible;":"verdict:compatible;"; $TestResults .= ""; if($RESULT{$Level}{"Problems"}) { $TestResults .= ""; } else { $TestResults .= ""; } $TestResults .= "\n"; $TestResults .= "
Total Java ARchives$Checked_Archives_Link
Total Methods / Classes".keys(%CheckedMethods)." / ".keys(%{$LibClasses{1}})."
VerdictIncompatible
(".$RESULT{$Level}{"Affected"}."%)
Compatible
\n"; $META_DATA .= "affected:".$RESULT{$Level}{"Affected"}.";";# in percents # Problem Summary $Problem_Summary .= "

Problem Summary


"; $Problem_Summary .= ""; $Problem_Summary .= ""; if(not $ShortMode) { my $Added_Link = "0"; if($Added>0) { if($JoinReport) { $Added_Link = "$Added"; } else { $Added_Link = "$Added"; } } $META_DATA .= "added:$Added;"; $Problem_Summary .= "$Added_Link"; } my $Removed_Link = "0"; if($Removed>0) { if($JoinReport) { $Removed_Link = "$Removed" } else { $Removed_Link = "$Removed" } } $META_DATA .= "removed:$Removed;"; $Problem_Summary .= ""; $Problem_Summary .= "$Removed_Link"; my $TH_Link = "0"; $TH_Link = "$T_Problems_High" if($T_Problems_High>0); $META_DATA .= "type_problems_high:$T_Problems_High;"; $Problem_Summary .= ""; $Problem_Summary .= "$TH_Link"; my $TM_Link = "0"; $TM_Link = "$T_Problems_Medium" if($T_Problems_Medium>0); $META_DATA .= "type_problems_medium:$T_Problems_Medium;"; $Problem_Summary .= "$TM_Link"; my $TL_Link = "0"; $TL_Link = "$T_Problems_Low" if($T_Problems_Low>0); $META_DATA .= "type_problems_low:$T_Problems_Low;"; $Problem_Summary .= "$TL_Link"; my $MH_Link = "0"; $MH_Link = "$M_Problems_High" if($M_Problems_High>0); $META_DATA .= "method_problems_high:$M_Problems_High;"; $Problem_Summary .= ""; $Problem_Summary .= "$MH_Link"; my $MM_Link = "0"; $MM_Link = "$M_Problems_Medium" if($M_Problems_Medium>0); $META_DATA .= "method_problems_medium:$M_Problems_Medium;"; $Problem_Summary .= "$MM_Link"; my $ML_Link = "0"; $ML_Link = "$M_Problems_Low" if($M_Problems_Low>0); $META_DATA .= "method_problems_low:$M_Problems_Low;"; $Problem_Summary .= "$ML_Link"; if($CheckImpl and $Level eq "Binary" and not $Quick) { my $ChangedImpl_Link = "0"; $ChangedImpl_Link = "".keys(%ImplProblems)."" if(keys(%ImplProblems)>0); $META_DATA .= "changed_implementation:".keys(%ImplProblems).";"; $Problem_Summary .= "$ChangedImpl_Link"; $RESULT{$Level}{"Warnings"}+=keys(%ImplProblems); } # Safe Changes if($T_Other) { my $TS_Link = "$T_Other"; $Problem_Summary .= "$TS_Link\n"; } if($M_Other) { my $MS_Link = "$M_Other"; $Problem_Summary .= "$MS_Link\n"; } $META_DATA .= "tool_version:$TOOL_VERSION"; $Problem_Summary .= "
SeverityCount
Added Methods-
Removed MethodsHigh
Problems with
Data Types
High
Medium
Low
Problems with
Methods
High
Medium
Low
Problems with
Implementation
Low
Other Changes
in Data Types
-
Other Changes
in Methods
-
\n"; return ($TestInfo.$TestResults.$Problem_Summary, $META_DATA); } sub getStyle($$$) { my ($Subj, $Act, $Num) = @_; my %Style = ( "A"=>"new", "R"=>"failed", "S"=>"passed", "L"=>"warning", "M"=>"failed", "H"=>"failed" ); if($Num>0) { return " class='".$Style{$Act}."'"; } return ""; } sub get_Anchor($$$) { my ($Kind, $Level, $Severity) = @_; if($JoinReport) { if($Severity eq "Safe") { return "Other_".$Level."_Changes_In_".$Kind."s"; } else { return $Kind."_".$Level."_Problems_".$Severity; } } else { if($Severity eq "Safe") { return "Other_Changes_In_".$Kind."s"; } else { return $Kind."_Problems_".$Severity; } } } sub get_Report_Implementation() { my ($CHANGED_IMPLEMENTATION, %MethodInArchiveClass); foreach my $Method (sort keys(%ImplProblems)) { my $ArchiveName = $MethodInfo{1}{$Method}{"Archive"}; my $ClassName = get_ShortName($MethodInfo{1}{$Method}{"Class"}, 1); $MethodInArchiveClass{$ArchiveName}{$ClassName}{$Method} = 1; } my $Changed_Number = 0; foreach my $ArchiveName (sort {lc($a) cmp lc($b)} keys(%MethodInArchiveClass)) { foreach my $ClassName (sort {lc($a) cmp lc($b)} keys(%{$MethodInArchiveClass{$ArchiveName}})) { $CHANGED_IMPLEMENTATION .= "$ArchiveName, $ClassName.class
\n"; my %NameSpace_Method = (); foreach my $Method (keys(%{$MethodInArchiveClass{$ArchiveName}{$ClassName}})) { $NameSpace_Method{$MethodInfo{1}{$Method}{"Package"}}{$Method} = 1; } foreach my $NameSpace (sort keys(%NameSpace_Method)) { $CHANGED_IMPLEMENTATION .= ($NameSpace)?"package $NameSpace"."
\n":""; my @SortedMethods = sort {lc($MethodInfo{1}{$a}{"Signature"}) cmp lc($MethodInfo{1}{$b}{"Signature"})} keys(%{$NameSpace_Method{$NameSpace}}); foreach my $Method (@SortedMethods) { $Changed_Number += 1; my $Signature = $MethodInfo{1}{$Method}{"Signature"}; if($NameSpace) { $Signature=~s/(\W|\A)\Q$NameSpace\E\.(\w)/$1$2/g; } my $SubReport = insertIDs($ContentSpanStart.highLight_Signature_Italic_Color($Signature).$ContentSpanEnd."
\n".$ContentDivStart."[run-time name: ".htmlSpecChars($Method)."]".$ImplProblems{$Method}{"Diff"}."

".$ContentDivEnd."\n"); $CHANGED_IMPLEMENTATION .= $SubReport; } } $CHANGED_IMPLEMENTATION .= "
\n"; } } if($CHANGED_IMPLEMENTATION) { $CHANGED_IMPLEMENTATION = "

Problems with Implementation ($Changed_Number)


\n".$CHANGED_IMPLEMENTATION.$TOP_REF."
\n"; } return $CHANGED_IMPLEMENTATION; } sub get_Report_Added($) { return "" if($ShortMode); my $Level = $_[0]; my ($ADDED_METHODS, %MethodAddedInArchiveClass); foreach my $Method (sort keys(%CompatProblems)) { foreach my $Kind (sort keys(%{$CompatProblems{$Method}})) { if($Kind eq "Added_Method") { my $ArchiveName = $MethodInfo{2}{$Method}{"Archive"}; my $ClassName = get_ShortName($MethodInfo{2}{$Method}{"Class"}, 2); if($Level eq "Source") { if($ChangedReturnFromVoid{$Method}) { next; } } $MethodAddedInArchiveClass{$ArchiveName}{$ClassName}{$Method} = 1; } } } my $Added_Number = 0; foreach my $ArchiveName (sort {lc($a) cmp lc($b)} keys(%MethodAddedInArchiveClass)) { foreach my $ClassName (sort {lc($a) cmp lc($b)} keys(%{$MethodAddedInArchiveClass{$ArchiveName}})) { $ADDED_METHODS .= "$ArchiveName, ".htmlSpecChars($ClassName).".class
\n"; my %NameSpace_Method = (); foreach my $Method (keys(%{$MethodAddedInArchiveClass{$ArchiveName}{$ClassName}})) { $NameSpace_Method{$MethodInfo{2}{$Method}{"Package"}}{$Method} = 1; } foreach my $NameSpace (sort keys(%NameSpace_Method)) { $ADDED_METHODS .= ($NameSpace)?"package $NameSpace
\n":""; my @SortedMethods = sort {lc($MethodInfo{2}{$a}{"Signature"}) cmp lc($MethodInfo{2}{$b}{"Signature"})} keys(%{$NameSpace_Method{$NameSpace}}); foreach my $Method (@SortedMethods) { $Added_Number += 1; my $Signature = $MethodInfo{2}{$Method}{"Signature"}; if($NameSpace) { $Signature=~s/(\W|\A)\Q$NameSpace\E\.(\w)/$1$2/g; } $ADDED_METHODS .= insertIDs($ContentSpanStart.highLight_Signature_Italic_Color($Signature).$ContentSpanEnd."
\n".$ContentDivStart."[run-time name: ".htmlSpecChars($Method)."]

".$ContentDivEnd."\n"); } } $ADDED_METHODS .= "
\n"; } } if($ADDED_METHODS) { my $Anchor = ""; if($JoinReport) { $Anchor = ""; } $ADDED_METHODS = $Anchor."

Added Methods ($Added_Number)


\n".$ADDED_METHODS.$TOP_REF."
\n"; } return $ADDED_METHODS; } sub get_Report_Removed($) { my $Level = $_[0]; my ($REMOVED_METHODS, %MethodRemovedFromArchiveClass); foreach my $Method (sort keys(%CompatProblems)) { foreach my $Kind (sort keys(%{$CompatProblems{$Method}})) { if($Kind eq "Removed_Method") { if($Level eq "Source") { if($ChangedReturnFromVoid{$Method}) { next; } } my $ArchiveName = $MethodInfo{1}{$Method}{"Archive"}; my $ClassName = get_ShortName($MethodInfo{1}{$Method}{"Class"}, 1); $MethodRemovedFromArchiveClass{$ArchiveName}{$ClassName}{$Method} = 1; } } } my $Removed_Number = 0; foreach my $ArchiveName (sort {lc($a) cmp lc($b)} keys(%MethodRemovedFromArchiveClass)) { foreach my $ClassName (sort {lc($a) cmp lc($b)} keys(%{$MethodRemovedFromArchiveClass{$ArchiveName}})) { $REMOVED_METHODS .= "$ArchiveName, ".htmlSpecChars($ClassName).".class
\n"; my %NameSpace_Method = (); foreach my $Method (keys(%{$MethodRemovedFromArchiveClass{$ArchiveName}{$ClassName}})) { $NameSpace_Method{$MethodInfo{1}{$Method}{"Package"}}{$Method} = 1; } foreach my $NameSpace (sort keys(%NameSpace_Method)) { $REMOVED_METHODS .= ($NameSpace)?"package $NameSpace
\n":""; my @SortedMethods = sort {lc($MethodInfo{1}{$a}{"Signature"}) cmp lc($MethodInfo{1}{$b}{"Signature"})} keys(%{$NameSpace_Method{$NameSpace}}); foreach my $Method (@SortedMethods) { $Removed_Number += 1; my $Signature = $MethodInfo{1}{$Method}{"Signature"}; if($NameSpace) { $Signature=~s/(\W|\A)\Q$NameSpace\E\.(\w)/$1$2/g; } $REMOVED_METHODS .= insertIDs($ContentSpanStart.highLight_Signature_Italic_Color($Signature).$ContentSpanEnd."
\n".$ContentDivStart."[run-time name: ".htmlSpecChars($Method)."]

".$ContentDivEnd."\n"); } } $REMOVED_METHODS .= "
\n"; } } if($REMOVED_METHODS) { my $Anchor = ""; if($JoinReport) { $Anchor = ""; } $REMOVED_METHODS = $Anchor."

Removed Methods ($Removed_Number)


\n".$REMOVED_METHODS.$TOP_REF."
\n"; } return $REMOVED_METHODS; } sub get_Report_MethodProblems($$) { my ($TargetSeverity, $Level) = @_; my ($METHOD_PROBLEMS, %MethodInArchiveClass); foreach my $Method (sort keys(%CompatProblems)) { next if($Method=~/\A([^\@\$\?]+)[\@\$]+/ and defined $CompatProblems{$1}); foreach my $Kind (sort keys(%{$CompatProblems{$Method}})) { if($MethodProblems_Kind{$Level}{$Kind} and $Kind ne "Added_Method" and $Kind ne "Removed_Method") { my $ArchiveName = $MethodInfo{1}{$Method}{"Archive"}; my $ClassName = get_ShortName($MethodInfo{1}{$Method}{"Class"}, 1); $MethodInArchiveClass{$ArchiveName}{$ClassName}{$Method} = 1; } } } my $Problems_Number = 0; foreach my $ArchiveName (sort {lc($a) cmp lc($b)} keys(%MethodInArchiveClass)) { foreach my $ClassName (sort {lc($a) cmp lc($b)} keys(%{$MethodInArchiveClass{$ArchiveName}})) { my ($ARCHIVE_CLASS_REPORT, %NameSpace_Method) = (); foreach my $Method (keys(%{$MethodInArchiveClass{$ArchiveName}{$ClassName}})) { $NameSpace_Method{$MethodInfo{1}{$Method}{"Package"}}{$Method} = 1; } foreach my $NameSpace (sort keys(%NameSpace_Method)) { my $NAMESPACE_REPORT = ""; my @SortedMethods = sort {lc($MethodInfo{1}{$a}{"Signature"}) cmp lc($MethodInfo{1}{$b}{"Signature"})} keys(%{$NameSpace_Method{$NameSpace}}); foreach my $Method (@SortedMethods) { my $Signature = $MethodInfo{1}{$Method}{"Signature"}; my $ShortSignature = get_Signature($Method, 1, "Short"); my $ClassName_Full = get_TypeName($MethodInfo{1}{$Method}{"Class"}, 1); my $MethodProblemsReport = ""; my $ProblemNum = 1; foreach my $Kind (keys(%{$CompatProblems{$Method}})) { foreach my $Location (keys(%{$CompatProblems{$Method}{$Kind}})) { my %Problems = %{$CompatProblems{$Method}{$Kind}{$Location}}; my $Type_Name = $Problems{"Type_Name"}; my $Target = $Problems{"Target"}; my $Priority = getProblemSeverity($Level, $Kind, $Type_Name, $Target); if($Priority ne $TargetSeverity) { next; } my ($Change, $Effect) = ("", ""); my $Old_Value = htmlSpecChars($Problems{"Old_Value"}); my $New_Value = htmlSpecChars($Problems{"New_Value"}); my $Parameter_Position = $Problems{"Parameter_Position"}; my $Parameter_Position_Str = showPos($Parameter_Position); if($Kind eq "Method_Became_Static") { $Change = "Method became static.\n"; $Effect = "A client program may be interrupted by NoSuchMethodError exception."; } elsif($Kind eq "Method_Became_NonStatic") { $Change = "Method became non-static.\n"; if($Level eq "Binary") { $Effect = "A client program may be interrupted by NoSuchMethodError exception."; } else { $Effect = "Recompilation of a client program may be terminated with the message: non-static method ".htmlSpecChars($ShortSignature)." cannot be referenced from a static context."; } } elsif($Kind eq "Changed_Method_Return_From_Void") { $Change = "Return value type has been changed from void to ".htmlSpecChars($New_Value).".\n"; $Effect = "This method has been removed because the return type is part of the method signature."; } elsif($Kind eq "Static_Method_Became_Final") {# Source Only $Change = "Method became final.\n"; $Effect = "Recompilation of a client program may be terminated with the message: ".htmlSpecChars($ShortSignature)." in client class C cannot override ".htmlSpecChars($ShortSignature)." in ".htmlSpecChars($ClassName_Full)."; overridden method is final."; } elsif($Kind eq "NonStatic_Method_Became_Final") { $Change = "Method became final.\n"; if($Level eq "Binary") { $Effect = "A client program trying to reimplement this method may be interrupted by VerifyError exception."; } else { $Effect = "Recompilation of a client program may be terminated with the message: ".htmlSpecChars($ShortSignature)." in client class C cannot override ".htmlSpecChars($ShortSignature)." in ".htmlSpecChars($ClassName_Full)."; overridden method is final."; } } elsif($Kind eq "Method_Became_Abstract") { $Change = "Method became abstract.\n"; if($Level eq "Binary") { $Effect = "A client program trying to create an instance of the method's class may be interrupted by InstantiationError exception."; } else { $Effect = "Recompilation of a client program may be terminated with the message: A client class C is not abstract and does not override abstract method ".htmlSpecChars($ShortSignature)." in ".htmlSpecChars($ClassName_Full)."."; } } elsif($Kind eq "Method_Became_NonAbstract") { $Change = "Method became non-abstract.\n"; $Effect = "A client program may change behavior."; } elsif($Kind eq "Method_Became_Synchronized") { $Change = "Method became synchronized.\n"; $Effect = "A multi-threaded client program may change behavior."; } elsif($Kind eq "Method_Became_NonSynchronized") { $Change = "Method became non-synchronized.\n"; $Effect = "A multi-threaded client program may change behavior."; } elsif($Kind eq "Changed_Method_Access") { $Change = "Access level has been changed from ".htmlSpecChars($Old_Value)." to ".htmlSpecChars($New_Value)."."; if($Level eq "Binary") { $Effect = "A client program may be interrupted by IllegalAccessError exception."; } else { $Effect = "Recompilation of a client program may be terminated with the message: ".htmlSpecChars($ShortSignature)." has $New_Value access in ".htmlSpecChars($ClassName_Full)."."; } } elsif($Kind eq "Abstract_Method_Added_Checked_Exception") {# Source Only $Change = "Added ".htmlSpecChars($Target)." exception thrown.\n"; $Effect = "Recompilation of a client program may be terminated with the message: unreported exception ".htmlSpecChars($Target)." must be caught or declared to be thrown."; } elsif($Kind eq "NonAbstract_Method_Added_Checked_Exception") { $Change = "Added ".htmlSpecChars($Target)." exception thrown.\n"; if($Level eq "Binary") { $Effect = "A client program may be interrupted by added exception."; } else { $Effect = "Recompilation of a client program may be terminated with the message: unreported exception ".htmlSpecChars($Target)." must be caught or declared to be thrown."; } } elsif($Kind eq "Abstract_Method_Removed_Checked_Exception") {# Source Only $Change = "Removed ".htmlSpecChars($Target)." exception thrown.\n"; $Effect = "Recompilation of a client program may be terminated with the message: cannot override ".htmlSpecChars($ShortSignature)." in ".htmlSpecChars($ClassName_Full)."; overridden method does not throw ".htmlSpecChars($Target)."."; } elsif($Kind eq "NonAbstract_Method_Removed_Checked_Exception") { $Change = "Removed ".htmlSpecChars($Target)." exception thrown.\n"; if($Level eq "Binary") { $Effect = "A client program may change behavior because the removed exception will not be thrown any more and client will not catch and handle it."; } else { $Effect = "Recompilation of a client program may be terminated with the message: cannot override ".htmlSpecChars($ShortSignature)." in ".htmlSpecChars($ClassName_Full)."; overridden method does not throw ".htmlSpecChars($Target)."."; } } elsif($Kind eq "Added_Unchecked_Exception") {# Binary Only $Change = "Added ".htmlSpecChars($Target)." exception thrown.\n"; $Effect = "A client program may be interrupted by added exception."; } elsif($Kind eq "Removed_Unchecked_Exception") {# Binary Only $Change = "Removed ".htmlSpecChars($Target)." exception thrown.\n"; $Effect = "A client program may change behavior because the removed exception will not be thrown any more and client will not catch and handle it."; } if($Change) { $MethodProblemsReport .= "$ProblemNum".$Change."".$Effect."\n"; $ProblemNum += 1; $Problems_Number += 1; } } } $ProblemNum -= 1; if($MethodProblemsReport) { $NAMESPACE_REPORT .= $ContentSpanStart."[+] ".highLight_Signature_Italic_Color($Signature)." ($ProblemNum)".$ContentSpanEnd."
\n$ContentDivStart  [run-time name: ".htmlSpecChars($Method)."]
\n"; if($NameSpace) { $NAMESPACE_REPORT=~s/(\W|\A)\Q$NameSpace\E\.(\w)/$1$2/g; } $NAMESPACE_REPORT .= "$MethodProblemsReport
ChangeEffect

$ContentDivEnd\n"; $NAMESPACE_REPORT = insertIDs($NAMESPACE_REPORT); } } if($NAMESPACE_REPORT) { $ARCHIVE_CLASS_REPORT .= (($NameSpace)?"package $NameSpace"."
\n":"").$NAMESPACE_REPORT; } } if($ARCHIVE_CLASS_REPORT) { $METHOD_PROBLEMS .= "$ArchiveName, $ClassName
\n".$ARCHIVE_CLASS_REPORT."
"; } } } if($METHOD_PROBLEMS) { my $Title = "Problems with Methods, $TargetSeverity Severity"; if($TargetSeverity eq "Safe") { # Safe Changes $Title = "Other Changes in Methods"; } $METHOD_PROBLEMS = "\n

$Title ($Problems_Number)


\n".$METHOD_PROBLEMS.$TOP_REF."
\n"; } return $METHOD_PROBLEMS; } sub get_Report_TypeProblems($$) { my ($TargetSeverity, $Level) = @_; my ($TYPE_PROBLEMS, %TypeArchive, %TypeChanges) = (); foreach my $Method (sort keys(%CompatProblems)) { foreach my $Kind (sort keys(%{$CompatProblems{$Method}})) { if($TypeProblems_Kind{$Level}{$Kind}) { foreach my $Location (sort keys(%{$CompatProblems{$Method}{$Kind}})) { my $Type_Name = $CompatProblems{$Method}{$Kind}{$Location}{"Type_Name"}; my $Target = $CompatProblems{$Method}{$Kind}{$Location}{"Target"}; my $Severity = getProblemSeverity($Level, $Kind, $Type_Name, $Target); if($Severity eq "Safe" and $TargetSeverity ne "Safe") { next; } if(cmpSeverities($Type_MaxPriority{$Level}{$Type_Name}{$Kind}{$Target}, $Severity)) {# select a problem with the highest priority next; } %{$TypeChanges{$Type_Name}{$Kind}{$Location}} = %{$CompatProblems{$Method}{$Kind}{$Location}}; my $ArchiveName = $TypeInfo{1}{$TName_Tid{1}{$Type_Name}}{"Archive"}; $TypeArchive{$ArchiveName}{$Type_Name} = 1; } } } } my @Methods = sort {lc($tr_name{$a}) cmp lc($tr_name{$b})} keys(%CompatProblems); my $Problems_Number = 0; foreach my $ArchiveName (sort {lc($a) cmp lc($b)} keys(%TypeArchive)) { my ($HEADER_REPORT, %NameSpace_Type) = (); foreach my $TypeName (keys(%{$TypeArchive{$ArchiveName}})) { $NameSpace_Type{$TypeInfo{1}{$TName_Tid{1}{$TypeName}}{"Package"}}{$TypeName} = 1; } foreach my $NameSpace (sort keys(%NameSpace_Type)) { my $NAMESPACE_REPORT = ""; my @SortedTypes = sort {lc($a) cmp lc($b)} keys(%{$NameSpace_Type{$NameSpace}}); foreach my $TypeName (@SortedTypes) { my $ProblemNum = 1; my ($TypeProblemsReport, %Kinds_Locations, %Kinds_Target) = (); foreach my $Kind (sort keys(%{$TypeChanges{$TypeName}})) { foreach my $Location (sort keys(%{$TypeChanges{$TypeName}{$Kind}})) { my $Target = $TypeChanges{$TypeName}{$Kind}{$Location}{"Target"}; my $Priority = getProblemSeverity($Level, $Kind, $TypeName, $Target); if($Priority ne $TargetSeverity) { next; } $Kinds_Locations{$Kind}{$Location} = 1; my ($Change, $Effect) = ("", ""); my %Problems = %{$TypeChanges{$TypeName}{$Kind}{$Location}}; next if($Kinds_Target{$Kind}{$Target}); $Kinds_Target{$Kind}{$Target} = 1; my $Old_Value = $Problems{"Old_Value"}; my $New_Value = $Problems{"New_Value"}; my $Field_Type = $Problems{"Field_Type"}; my $Field_Value = $Problems{"Field_Value"}; my $Type_Type = $Problems{"Type_Type"}; my $Add_Effect = $Problems{"Add_Effect"}; if($Kind eq "NonAbstract_Class_Added_Abstract_Method") { my $ShortSignature = get_Signature($Target, 2, "Short"); my $ClassName_Full = get_TypeName($MethodInfo{2}{$Target}{"Class"}, 2); $Change = "Abstract method ".black_name($MethodInfo{2}{$Target}{"Signature"})." has been added to this $Type_Type."; if($Level eq "Binary") { $Effect = "This class became abstract and a client program may be interrupted by InstantiationError exception."; } else { $Effect = "Recompilation of a client program may be terminated with the message: a client class C is not abstract and does not override abstract method ".htmlSpecChars($ShortSignature)." in ".htmlSpecChars($ClassName_Full)."."; } } elsif($Kind eq "Abstract_Class_Added_Abstract_Method") { my $ShortSignature = get_Signature($Target, 2, "Short"); my $ClassName_Full = get_TypeName($MethodInfo{2}{$Target}{"Class"}, 2); $Change = "Abstract method ".black_name($MethodInfo{2}{$Target}{"Signature"})." has been added to this $Type_Type."; if($Level eq "Binary") { if($Add_Effect) { $Effect = "A client program may be interrupted by AbstractMethodError exception.".$Add_Effect; } else { $Effect = "No effect."; } } else { $Effect = "Recompilation of a client program may be terminated with the message: a client class C is not abstract and does not override abstract method ".htmlSpecChars($ShortSignature)." in ".htmlSpecChars($ClassName_Full)."."; } } elsif($Kind eq "Class_Removed_Abstract_Method" or $Kind eq "Interface_Removed_Abstract_Method") { my $ShortSignature = get_Signature($Target, 1, "Short"); my $ClassName_Full = get_TypeName($MethodInfo{1}{$Target}{"Class"}, 1); $Change = "Abstract method ".black_name($MethodInfo{1}{$Target}{"Signature"})." has been removed from this $Type_Type."; if($Level eq "Binary") { $Effect = "A client program may be interrupted by NoSuchMethodError exception."; } else { $Effect = "Recompilation of a client program may be terminated with the message: cannot find method ".htmlSpecChars($ShortSignature)." in $Type_Type ".htmlSpecChars($ClassName_Full)."."; } } elsif($Kind eq "Interface_Added_Abstract_Method") { my $ShortSignature = get_Signature($Target, 2, "Short"); my $ClassName_Full = get_TypeName($MethodInfo{2}{$Target}{"Class"}, 2); $Change = "Abstract method ".black_name($MethodInfo{2}{$Target}{"Signature"})." has been added to this $Type_Type."; if($Level eq "Binary") { if($Add_Effect) { $Effect = "A client program may be interrupted by AbstractMethodError exception.".$Add_Effect; } else { $Effect = "No effect."; } } else { $Effect = "Recompilation of a client program may be terminated with the message: a client class C is not abstract and does not override abstract method ".htmlSpecChars($ShortSignature)." in ".htmlSpecChars($ClassName_Full)."."; } } elsif($Kind eq "Class_Method_Became_Abstract") { my $ShortSignature = get_Signature($Target, 1, "Short"); my $ClassName_Full = get_TypeName($MethodInfo{1}{$Target}{"Class"}, 1); $Change = "Method ".black_name($MethodInfo{1}{$Target}{"Signature"})." became abstract."; if($Level eq "Binary") { $Effect = "A client program may be interrupted by InstantiationError exception."; } else { $Effect = "Recompilation of a client program may be terminated with the message: a client class C is not abstract and does not override abstract method ".htmlSpecChars($ShortSignature)." in ".htmlSpecChars($ClassName_Full)."."; } } elsif($Kind eq "Class_Method_Became_NonAbstract") { $Change = "Abstract method ".black_name($MethodInfo{1}{$Target}{"Signature"})." became non-abstract."; $Effect = "Some methods in this class may change behavior."; } elsif($Kind eq "Class_Overridden_Method") { $Change = "Method ".black_name($Old_Value)." has been overridden by ".black_name($New_Value); $Effect = "Method ".black_name($New_Value)." will be called instead of ".black_name($Old_Value)." in a client program."; } elsif($Kind eq "Class_Method_Moved_Up_Hierarchy") { $Change = "Method ".black_name($Old_Value)." has been moved up type hierarchy to ".black_name($New_Value); $Effect = "Method ".black_name($New_Value)." will be called instead of ".black_name($Old_Value)." in a client program."; } elsif($Kind eq "Abstract_Class_Added_Super_Interface") { $Change = "Added super-interface ".htmlSpecChars($Target)."."; if($Level eq "Binary") { if($Add_Effect) { $Effect = "If abstract methods from an added super-interface must be implemented by client then it may be interrupted by AbstractMethodError exception.".$Add_Effect; } else { $Effect = "No effect."; } } else { $Effect = "Recompilation of a client program may be terminated with the message: a client class C is not abstract and does not override abstract method in ".htmlSpecChars($TypeName)."."; } } elsif($Kind eq "Interface_Added_Super_Interface") { $Change = "Added super-interface ".htmlSpecChars($Target)."."; if($Level eq "Binary") { if($Add_Effect) { $Effect = "If abstract methods from an added super-interface must be implemented by client then it may be interrupted by AbstractMethodError exception.".$Add_Effect; } else { $Effect = "No effect."; } } else { $Effect = "Recompilation of a client program may be terminated with the message: a client class C is not abstract and does not override abstract method in ".htmlSpecChars($Target)."."; } } elsif($Kind eq "Interface_Added_Super_Constant_Interface") { $Change = "Added super-interface ".htmlSpecChars($Target)." containing constants only."; if($Level eq "Binary") { $Effect = "A static field from a super-interface of a client class may hide a field (with the same name) inherited from a super-class and cause IncompatibleClassChangeError exception."; } else { $Effect = "A static field from a super-interface of a client class may hide a field (with the same name) inherited from a super-class. Recompilation of a client class may be terminated with the message: reference to variable is ambiguous."; } } elsif($Kind eq "Interface_Removed_Super_Interface" or $Kind eq "Class_Removed_Super_Interface") { $Change = "Removed super-interface ".htmlSpecChars($Target)."."; if($Level eq "Binary") { $Effect = "A client program may be interrupted by NoSuchMethodError exception."; } else { $Effect = "Recompilation of a client program may be terminated with the message: cannot find method in $Type_Type ".htmlSpecChars($TypeName)."."; } } elsif($Kind eq "Interface_Removed_Super_Constant_Interface") {# Source Only $Change = "Removed super-interface ".htmlSpecChars($Target)." containing constants only."; $Effect = "Recompilation of a client program may be terminated with the message: cannot find variable in $Type_Type ".htmlSpecChars($TypeName)."."; } elsif($Kind eq "Added_Super_Class") { $Change = "Added super-class ".htmlSpecChars($Target)."."; if($Level eq "Binary") { $Effect = "A static field from a super-interface of a client class may hide a field (with the same name) inherited from new super-class and cause IncompatibleClassChangeError exception."; } else { $Effect = "A static field from a super-interface of a client class may hide a field (with the same name) inherited from new super-class. Recompilation of a client class may be terminated with the message: reference to variable is ambiguous."; } } elsif($Kind eq "Abstract_Class_Added_Super_Abstract_Class") { $Change = "Added abstract super-class ".htmlSpecChars($Target)."."; if($Level eq "Binary") { if($Add_Effect) { $Effect = "If abstract methods from an added super-class must be implemented by client then it may be interrupted by AbstractMethodError exception.".$Add_Effect; } else { $Effect = "No effect."; } } else { $Effect = "Recompilation of a client program may be terminated with the message: a client class C is not abstract and does not override abstract method in ".htmlSpecChars($Target)."."; } } elsif($Kind eq "Removed_Super_Class") { $Change = "Removed super-class ".htmlSpecChars($Target)."."; if($Level eq "Binary") { $Effect = "Access of a client program to the fields or methods of the old super-class may be interrupted by NoSuchFieldError or NoSuchMethodError exceptions."; } else { $Effect = "Recompilation of a client program may be terminated with the message: cannot find variable (or method) in ".htmlSpecChars($TypeName)."."; } } elsif($Kind eq "Changed_Super_Class") { $Change = "Superclass has been changed from ".htmlSpecChars($Old_Value)." to ".htmlSpecChars($New_Value)."."; if($Level eq "Binary") { $Effect = "1) Access of a client program to the fields or methods of the old super-class may be interrupted by NoSuchFieldError or NoSuchMethodError exceptions.
2) A static field from a super-interface of a client class may hide a field (with the same name) inherited from new super-class and cause IncompatibleClassChangeError exception."; } else { $Effect = "1) Recompilation of a client program may be terminated with the message: cannot find variable (or method) in ".htmlSpecChars($TypeName).".
2) A static field from a super-interface of a client class may hide a field (with the same name) inherited from new super-class. Recompilation of a client class may be terminated with the message: reference to variable is ambiguous."; } } elsif($Kind eq "Class_Added_Field") { $Change = "Field $Target has been added to this class."; if($Level eq "Binary") { $Effect = "No effect.
NOTE: A static field from a super-interface of a client class may hide an added field (with the same name) inherited from the super-class of a client class and cause IncompatibleClassChangeError exception."; } else { $Effect = "No effect.
NOTE: A static field from a super-interface of a client class may hide an added field (with the same name) inherited from the super-class of a client class. Recompilation of a client class may be terminated with the message: reference to $Target is ambiguous."; } } elsif($Kind eq "Interface_Added_Field") { $Change = "Field $Target has been added to this interface."; if($Level eq "Binary") { $Effect = "No effect.
NOTE: An added static field from a super-interface of a client class may hide a field (with the same name) inherited from the super-class of a client class and cause IncompatibleClassChangeError exception."; } else { $Effect = "No effect.
NOTE: An added static field from a super-interface of a client class may hide a field (with the same name) inherited from the super-class of a client class. Recompilation of a client class may be terminated with the message: reference to $Target is ambiguous."; } } elsif($Kind eq "Renamed_Field") { $Change = "Field $Target has been renamed to ".htmlSpecChars($New_Value)."."; if($Level eq "Binary") { $Effect = "A client program may be interrupted by NoSuchFieldError exception."; } else { $Effect = "Recompilation of a client program may be terminated with the message: cannot find variable $Target in ".htmlSpecChars($TypeName)."."; } } elsif($Kind eq "Renamed_Constant_Field") { if($Level eq "Binary") { $Change = "Field $Target (".htmlSpecChars($Field_Type).") with the compile-time constant value $Field_Value has been renamed to ".htmlSpecChars($New_Value)."."; $Effect = "A client program may change behavior."; } else { $Change = "Field $Target has been renamed to ".htmlSpecChars($New_Value)."."; $Effect = "Recompilation of a client program may be terminated with the message: cannot find variable $Target in ".htmlSpecChars($TypeName)."."; } } elsif($Kind eq "Removed_NonConstant_Field") { $Change = "Field $Target (".htmlSpecChars($Field_Type).") has been removed from this $Type_Type."; if($Level eq "Binary") { $Effect = "A client program may be interrupted by NoSuchFieldError exception."; } else { $Effect = "Recompilation of a client program may be terminated with the message: cannot find variable $Target in ".htmlSpecChars($TypeName)."."; } } elsif($Kind eq "Removed_Constant_Field") { $Change = "Field $Target (".htmlSpecChars($Field_Type).") with the compile-time constant value $Field_Value has been removed from this $Type_Type."; if($Level eq "Binary") { $Effect = "A client program may change behavior."; } else { $Effect = "Recompilation of a client program may be terminated with the message: cannot find variable $Target in ".htmlSpecChars($TypeName)."."; } } elsif($Kind eq "Changed_Field_Type") { $Change = "Type of field $Target has been changed from ".htmlSpecChars($Old_Value)." to ".htmlSpecChars($New_Value)."."; if($Level eq "Binary") { $Effect = "A client program may be interrupted by NoSuchFieldError exception."; } else { $Effect = "Recompilation of a client program may be terminated with the message: incompatible types, found: ".htmlSpecChars($Old_Value).", required: ".htmlSpecChars($New_Value)."."; } } elsif($Kind eq "Changed_Field_Access") { $Change = "Access level of field $Target has been changed from $Old_Value to $New_Value."; if($Level eq "Binary") { $Effect = "A client program may be interrupted by IllegalAccessError exception."; } else { if($New_Value eq "package-private") { $Effect = "Recompilation of a client program may be terminated with the message: $Target is not public in ".htmlSpecChars($TypeName)."; cannot be accessed from outside package."; } else { $Effect = "Recompilation of a client program may be terminated with the message: $Target has $New_Value access in ".htmlSpecChars($TypeName)."."; } } } elsif($Kind eq "Changed_Final_Field_Value") { # Binary Only $Change = "Value of final field $Target ($Field_Type) has been changed from ".htmlSpecChars($Old_Value)." to ".htmlSpecChars($New_Value)."."; $Effect = "Old value of the field will be inlined to the client code at compile-time and will be used instead of a new one."; } elsif($Kind eq "Field_Became_Final") { $Change = "Field $Target became final."; if($Level eq "Binary") { $Effect = "A client program may be interrupted by IllegalAccessError exception when attempt to assign new values to the field."; } else { $Effect = "Recompilation of a client program may be terminated with the message: cannot assign a value to final variable $Target."; } } elsif($Kind eq "Field_Became_NonFinal") { # Binary Only $Change = "Field $Target became non-final."; $Effect = "Old value of the field will be inlined to the client code at compile-time and will be used instead of a new one."; } elsif($Kind eq "NonConstant_Field_Became_Static") { # Binary Only $Change = "Non-final field $Target became static."; $Effect = "A client program may be interrupted by IncompatibleClassChangeError exception."; } elsif($Kind eq "NonConstant_Field_Became_NonStatic") { if($Level eq "Binary") { $Change = "Non-constant field $Target became non-static."; $Effect = "A client program may be interrupted by IncompatibleClassChangeError exception."; } else { $Change = "Field $Target became non-static."; $Effect = "Recompilation of a client program may be terminated with the message: non-static variable $Target cannot be referenced from a static context."; } } elsif($Kind eq "Constant_Field_Became_NonStatic") { # Source Only $Change = "Field $Target became non-static."; $Effect = "Recompilation of a client program may be terminated with the message: non-static variable $Target cannot be referenced from a static context."; } elsif($Kind eq "Class_Became_Interface") { $Change = "This class became interface."; if($Level eq "Binary") { $Effect = "A client program may be interrupted by IncompatibleClassChangeError or InstantiationError exception dependent on the usage of this class."; } else { $Effect = "Recompilation of a client program may be terminated with the message: ".htmlSpecChars($TypeName)." is abstract; cannot be instantiated."; } } elsif($Kind eq "Interface_Became_Class") { $Change = "This interface became class."; if($Level eq "Binary") { $Effect = "A client program may be interrupted by IncompatibleClassChangeError exception."; } else { $Effect = "Recompilation of a client program may be terminated with the message: interface expected."; } } elsif($Kind eq "Class_Became_Final") { $Change = "This class became final."; if($Level eq "Binary") { $Effect = "A client program may be interrupted by VerifyError exception."; } else { $Effect = "Recompilation of a client program may be terminated with the message: cannot inherit from final ".htmlSpecChars($TypeName)."."; } } elsif($Kind eq "Class_Became_Abstract") { $Change = "This class became abstract."; if($Level eq "Binary") { $Effect = "A client program may be interrupted by InstantiationError exception."; } else { $Effect = "Recompilation of a client program may be terminated with the message: ".htmlSpecChars($TypeName)." is abstract; cannot be instantiated."; } } elsif($Kind eq "Removed_Class") { $Change = "This class has been removed."; if($Level eq "Binary") { $Effect = "A client program may be interrupted by NoClassDefFoundError exception."; } else { $Effect = "Recompilation of a client program may be terminated with the message: cannot find class ".htmlSpecChars($TypeName)."."; } } elsif($Kind eq "Removed_Interface") { $Change = "This interface has been removed."; if($Level eq "Binary") { $Effect = "A client program may be interrupted by NoClassDefFoundError exception."; } else { $Effect = "Recompilation of a client program may be terminated with the message: cannot find class ".htmlSpecChars($TypeName)."."; } } if($Change) { $TypeProblemsReport .= "$ProblemNum".$Change."".$Effect."\n"; $ProblemNum += 1; $Problems_Number += 1; $Kinds_Locations{$Kind}{$Location} = 1; } } } $ProblemNum -= 1; if($TypeProblemsReport) { my $Affected = getAffectedMethods($Level, $TypeName, \%Kinds_Locations, \@Methods); $NAMESPACE_REPORT .= $ContentSpanStart."[+] ".htmlSpecChars($TypeName)." ($ProblemNum)".$ContentSpanEnd."
\n"; $NAMESPACE_REPORT .= $ContentDivStart.""; $NAMESPACE_REPORT .= ""; $NAMESPACE_REPORT .= "$TypeProblemsReport
ChangeEffect
".$Affected."

$ContentDivEnd\n"; $NAMESPACE_REPORT = insertIDs($NAMESPACE_REPORT); if($NameSpace) { $NAMESPACE_REPORT=~s/(\W|\A)\Q$NameSpace\E\.(\w)/$1$2/g; } } } if($NAMESPACE_REPORT) { if($NameSpace) { $NAMESPACE_REPORT = "package ".$NameSpace."
\n".$NAMESPACE_REPORT } if($HEADER_REPORT) { $NAMESPACE_REPORT = "
".$NAMESPACE_REPORT; } $HEADER_REPORT .= $NAMESPACE_REPORT; } } if($HEADER_REPORT) { $TYPE_PROBLEMS .= "$ArchiveName
\n".$HEADER_REPORT."
"; } } if($TYPE_PROBLEMS) { my $Title = "Problems with Data Types, $TargetSeverity Severity"; if($TargetSeverity eq "Safe") { # Safe Changes $Title = "Other Changes in Data Types"; } $TYPE_PROBLEMS = "\n

$Title ($Problems_Number)


\n".$TYPE_PROBLEMS.$TOP_REF."
\n"; } return $TYPE_PROBLEMS; } sub getAffectedMethods($$$$) { my ($Level, $Target_TypeName, $Kinds_Locations, $Methods) = @_; my ($Affected, %INumber) = (); my $LIMIT = 1000; if(defined $AffectLimit) { $LIMIT = $AffectLimit; } elsif(defined $ShortMode) { $AffectLimit = 10; $LIMIT = $AffectLimit; } else { if($#{$Methods}>=1999) { # reduce size of the report $AffectLimit = 10; printMsg("WARNING", "reducing limit of affected symbols shown in the report to $AffectLimit"); $LIMIT = $AffectLimit; } } LOOP: foreach my $Method (@{$Methods}) { foreach my $Kind (keys(%{$Kinds_Locations})) { if(not defined $CompatProblems{$Method} or not defined $CompatProblems{$Method}{$Kind}) { next; } foreach my $Location (keys(%{$Kinds_Locations->{$Kind}})) { if(keys(%INumber)>$LIMIT) { last LOOP; } if(not defined $CompatProblems{$Method}{$Kind}{$Location}) { next; } next if(defined $INumber{$Method}); my $Type_Name = $CompatProblems{$Method}{$Kind}{$Location}{"Type_Name"}; next if($Type_Name ne $Target_TypeName); $INumber{$Method} = 1; my $Param_Pos = $CompatProblems{$Method}{$Kind}{$Location}{"Parameter_Position"}; my $Description = getAffectDesc($Method, $Kind, $Location, $Level); $Affected .= "".highLight_Signature_PPos_Italic($MethodInfo{1}{$Method}{"Signature"}, $Param_Pos, 1, 0)."
"."
".$Description."
\n"; } } } $Affected = "
".$Affected."
"; if(keys(%INumber)>$LIMIT) { $Affected .= "and others ...
"; } if($Affected) { $Affected = $ContentDivStart.$Affected.$ContentDivEnd; $Affected = $ContentSpanStart_Affected."[+] affected methods (".(keys(%INumber)>$LIMIT?"more than $LIMIT":keys(%INumber).")").$ContentSpanEnd.$Affected; } return ($Affected); } sub getAffectDesc($$$$) { my ($Method, $Kind, $Location, $Level) = @_; my %Affect = %{$CompatProblems{$Method}{$Kind}{$Location}}; my $Signature = $MethodInfo{1}{$Method}{"Signature"}; my $Old_Value = $Affect{"Old_Value"}; my $New_Value = $Affect{"New_Value"}; my $Type_Name = $Affect{"Type_Name"}; my $Parameter_Name = $Affect{"Parameter_Name"}; my $Parameter_Position_Str = showPos($Affect{"Parameter_Position"}); my @Sentence_Parts = (); my $Location_I = $Location; $Location=~s/\.[^.]+?\Z//; my %TypeAttr = get_Type($MethodInfo{1}{$Method}{"Class"}, 1); my $Type_Type = $TypeAttr{"Type"}; my $ABSTRACT_M = $MethodInfo{1}{$Method}{"Abstract"}?" abstract":""; my $ABSTRACT_C = $TypeAttr{"Abstract"}?" abstract":""; my $METHOD_TYPE = $MethodInfo{1}{$Method}{"Constructor"}?"constructor":"method"; if($Kind eq "Class_Overridden_Method" or $Kind eq "Class_Method_Moved_Up_Hierarchy") { return "Method '".highLight_Signature($New_Value)."' will be called instead of this method in a client program."; } elsif($TypeProblems_Kind{$Level}{$Kind}) { my %MInfo = %{$MethodInfo{1}{$Method}}; if($Location eq "this") { return "This$ABSTRACT_M $METHOD_TYPE is from \'".htmlSpecChars($Type_Name)."\'$ABSTRACT_C $Type_Type."; } my $TypeID = undef; if($Location=~/retval/) { # return value if($Location=~/\./) { push(@Sentence_Parts, "Field \'".htmlSpecChars($Location)."\' in return value"); } else { push(@Sentence_Parts, "Return value"); } $TypeID = $MInfo{"Return"}; } elsif($Location=~/this/) { # "this" reference push(@Sentence_Parts, "Field \'".htmlSpecChars($Location)."\' in the object"); $TypeID = $MInfo{"Class"}; } else { # parameters my $PName = getParamName($Location); my $PPos = getParamPos($PName, $Method, 1); if($Location=~/\./) { push(@Sentence_Parts, "Field \'".htmlSpecChars($Location)."\' in $Parameter_Position_Str parameter"); } else { push(@Sentence_Parts, "$Parameter_Position_Str parameter"); } if($Parameter_Name) { push(@Sentence_Parts, "\'$Parameter_Name\'"); } $TypeID = $MInfo{"Param"}{$PPos}{"Type"}; } push(@Sentence_Parts, " of this$ABSTRACT_M method"); my $Location_T = $Location; $Location_T=~s/\A\w+(\.|\Z)//; # location in type my $TypeID_Problem = $TypeID; if($Location_T) { $TypeID_Problem = getFieldType($Location_T, $TypeID, 1); } if($TypeInfo{1}{$TypeID_Problem}{"Name"} eq $Type_Name) { push(@Sentence_Parts, "has type \'".htmlSpecChars($Type_Name)."\'."); } else { push(@Sentence_Parts, "has base type \'".htmlSpecChars($Type_Name)."\'."); } } return join(" ", @Sentence_Parts); } sub getParamPos($$$) { my ($Name, $Method, $LibVersion) = @_; if(defined $MethodInfo{$LibVersion}{$Method} and defined $MethodInfo{$LibVersion}{$Method}{"Param"}) { my $Info = $MethodInfo{$LibVersion}{$Method}; foreach (keys(%{$Info->{"Param"}})) { if($Info->{"Param"}{$_}{"Name"} eq $Name) { return $_; } } } return undef; } sub getParamName($) { my $Loc = $_[0]; $Loc=~s/\..*//g; return $Loc; } sub getFieldType($$$) { my ($Location, $TypeId, $LibVersion) = @_; my @Fields = split(/\./, $Location); foreach my $Name (@Fields) { my %Info = get_BaseType($TypeId, $LibVersion); foreach my $N (keys(%{$Info{"Fields"}})) { if($N eq $Name) { $TypeId = $Info{"Fields"}{$N}{"Type"}; last; } } } return $TypeId; } sub writeReport($$) { my ($Level, $Report) = @_; my $RPath = getReportPath($Level); writeFile($RPath, $Report); if($Browse or $OpenReport) { # open in browser openReport($RPath); if($JoinReport or $DoubleReport) { if($Level eq "Binary") { # wait to open a browser sleep(1); } } } } sub openReport($) { my $Path = $_[0]; my $Cmd = ""; if($Browse) { # user-defined browser $Cmd = $Browse." \"$Path\""; } if(not $Cmd) { # default browser if($OSgroup eq "macos") { $Cmd = "open \"$Path\""; } elsif($OSgroup eq "windows") { $Cmd = "start ".path_format($Path, $OSgroup); } else { # linux, freebsd, solaris my @Browsers = ( "x-www-browser", "sensible-browser", "firefox", "opera", "xdg-open", "lynx", "links" ); foreach my $Br (@Browsers) { if($Br = get_CmdPath($Br)) { $Cmd = $Br." \"$Path\""; last; } } } } if($Cmd) { if($Debug) { printMsg("INFO", "running $Cmd"); } if($OSgroup ne "windows" and $OSgroup ne "macos") { if($Cmd!~/lynx|links/) { $Cmd .= " >\"$TMP_DIR/null\" 2>&1 &"; } } system($Cmd); } else { printMsg("ERROR", "cannot open report in browser"); } } sub createReport() { if($JoinReport) { # --stdout writeReport("Join", getReport("Join")); } elsif($DoubleReport) { # default writeReport("Binary", getReport("Binary")); writeReport("Source", getReport("Source")); } elsif($BinaryOnly) { # --binary writeReport("Binary", getReport("Binary")); } elsif($SourceOnly) { # --source writeReport("Source", getReport("Source")); } } sub getReport($) { my $Level = $_[0]; my $CssStyles = " body { font-family:Arial, sans-serif; color:Black; font-size:14px; } hr { color:Black; background-color:Black; height:1px; border:0; } h1 { margin-bottom:0px; padding-bottom:0px; font-size:26px; } h2 { margin-bottom:0px; padding-bottom:0px; font-size:20px; white-space:nowrap; } span.section { font-weight:bold; cursor:pointer; font-size:16px; color:#003E69; white-space:nowrap; margin-left:5px; } span:hover.section { color:#336699; } span.section_affected { cursor:pointer; margin-left:7px; padding-left:15px; font-size:14px; color:#cc3300; } span.extension { font-weight:100; font-size:16px; } span.jar { color:#cc3300; font-size:14px; font-weight:bold; } div.class_list { padding-left:5px; font-size:15px; } div.jar_list { padding-left:5px; font-size:15px; } span.package_title { color:#408080; font-size:14px; } span.package_list { font-size:14px; } span.package { color:#408080; font-size:14px; font-weight:bold; } span.cname { color:Green; font-size:14px; font-weight:bold; } span.nblack { font-weight:bold; font-size:15px; } span.sym_p { font-weight:normal; white-space:normal; } span.sym_kind { color:Black; font-weight:normal; } div.affect { padding-left:15px; padding-bottom:4px; font-size:14px; font-style:italic; line-height:13px; } div.affected { padding-left:30px; padding-top:3px; } table.ptable { border-collapse:collapse; border:1px outset black; line-height:16px; margin-left:15px; margin-top:3px; margin-bottom:3px; width:900px; } table.ptable td { border:1px solid Gray; padding: 3px; } table.ptable th { background-color:#eeeeee; font-weight:bold; font-size:13px; font-family:Verdana; border:1px solid Gray; text-align:center; vertical-align:top; white-space:nowrap; padding: 3px; } td.code_line { padding-left:15px; text-align:left; white-space:nowrap; } table.code_view { cursor:text; margin-top:7px; width:50%; margin-left:20px; font-family:Consolas, 'DejaVu Sans Mono', 'Droid Sans Mono', Monaco, Monospace; font-size:14px; padding:10px; border:1px solid #e0e8e5; color:#444444; background-color:#eff3f2; overflow:auto; } table.summary { border-collapse:collapse; border:1px outset black; } table.summary th { background-color:#eeeeee; font-weight:100; text-align:left; font-size:15px; white-space:nowrap; border:1px inset gray; } table.summary td { padding-left:10px; padding-right:5px; text-align:right; font-size:16px; white-space:nowrap; border:1px inset gray; } span.mangled { padding-left:15px; font-size:14px; cursor:text; color:#444444; } span.color_p { font-style:italic; color:Brown; } span.param { font-style:italic; } span.focus_p { font-style:italic; color:Red; } span.nowrap { white-space:nowrap; } td.passed { background-color:#CCFFCC; } td.warning { background-color:#F4F4AF; } td.failed { background-color:#FFCCCC; } td.new { background-color:#C6DEFF; }"; my $JScripts = " function showContent(header, id) { e = document.getElementById(id); if(e.style.display == 'none') { e.style.display = 'block'; e.style.visibility = 'visible'; header.innerHTML = header.innerHTML.replace(/\\\[[^0-9 ]\\\]/gi,\"[−]\"); } else { e.style.display = 'none'; e.style.visibility = 'hidden'; header.innerHTML = header.innerHTML.replace(/\\\[[^0-9 ]\\\]/gi,\"[+]\"); } }"; if($JoinReport) { $CssStyles .= " .tabset { float:left; } a.tab { border:1px solid #AAA; float:left; margin:0px 5px -1px 0px; padding:3px 5px 3px 5px; position:relative; font-size:14px; background-color:#DDD; text-decoration:none; color:Black; } a.disabled:hover { color:Black; background:#EEE; } a.active:hover { color:Black; background:White; } a.active { border-bottom-color:White; background-color:White; } div.tab { border:1px solid #AAA; padding:0 7px 0 12px; width:97%; clear:both; }"; $JScripts .= " function initTabs() { var url = window.location.href; if(url.indexOf('_Source_')!=-1 || url.indexOf('#Source')!=-1) { var tab1 = document.getElementById('BinaryID'); var tab2 = document.getElementById('SourceID'); tab1.className='tab disabled'; tab2.className='tab active'; } var sets = document.getElementsByTagName('div'); for (var i = 0; i < sets.length; i++) { if (sets[i].className.indexOf('tabset') != -1) { var tabs = []; var links = sets[i].getElementsByTagName('a'); for (var j = 0; j < links.length; j++) { if (links[j].className.indexOf('tab') != -1) { tabs.push(links[j]); links[j].tabs = tabs; var tab = document.getElementById(links[j].href.substr(links[j].href.indexOf('#') + 1)); //reset all tabs on start if (tab) { if (links[j].className.indexOf('active')!=-1) { tab.style.display = 'block'; } else { tab.style.display = 'none'; } } links[j].onclick = function() { var tab = document.getElementById(this.href.substr(this.href.indexOf('#') + 1)); if (tab) { //reset all tabs before change for (var k = 0; k < this.tabs.length; k++) { document.getElementById(this.tabs[k].href.substr(this.tabs[k].href.indexOf('#') + 1)).style.display = 'none'; this.tabs[k].className = this.tabs[k].className.replace('active', 'disabled'); } this.className = 'tab active'; tab.style.display = 'block'; // window.location.hash = this.id.replace('ID', ''); return false; } } } } } } if(url.indexOf('#')!=-1) { location.href=location.href; } } if (window.addEventListener) window.addEventListener('load', initTabs, false); else if (window.attachEvent) window.attachEvent('onload', initTabs);"; } if($Level eq "Join") { my $Title = "$TargetLibraryFullName: ".$Descriptor{1}{"Version"}." to ".$Descriptor{2}{"Version"}." compatibility report"; my $Keywords = "$TargetLibraryFullName, compatibility"; my $Description = "Compatibility report for the $TargetLibraryFullName library between ".$Descriptor{1}{"Version"}." and ".$Descriptor{2}{"Version"}." versions"; my ($BSummary, $BMetaData) = get_Summary("Binary"); my ($SSummary, $SMetaData) = get_Summary("Source"); my $Report = "\n\n".composeHTML_Head($Title, $Keywords, $Description, $CssStyles, $JScripts).""; $Report .= get_Report_Header("Join")."
"; $Report .= "
\n$BSummary\n".get_Report_Added("Binary").get_Report_Removed("Binary").get_Report_Problems("High", "Binary").get_Report_Problems("Medium", "Binary").get_Report_Problems("Low", "Binary").get_Report_Problems("Safe", "Binary").get_SourceInfo()."


"; $Report .= "
\n$SSummary\n".get_Report_Added("Source").get_Report_Removed("Source").get_Report_Problems("High", "Source").get_Report_Problems("Medium", "Source").get_Report_Problems("Low", "Source").get_Report_Problems("Safe", "Source").get_SourceInfo()."


"; $Report .= getReportFooter($TargetLibraryFullName); $Report .= "\n
\n"; return $Report; } else { my ($Summary, $MetaData) = get_Summary($Level); my $Title = "$TargetLibraryFullName: ".$Descriptor{1}{"Version"}." to ".$Descriptor{2}{"Version"}." ".lc($Level)." compatibility report"; my $Keywords = "$TargetLibraryFullName, ".lc($Level).", compatibility"; my $Description = "$Level compatibility report for the $TargetLibraryFullName library between ".$Descriptor{1}{"Version"}." and ".$Descriptor{2}{"Version"}." versions"; my $Report = "\n".composeHTML_Head($Title, $Keywords, $Description, $CssStyles, $JScripts).""; $Report .= get_Report_Header($Level)."\n".$Summary."\n"; $Report .= get_Report_Added($Level).get_Report_Removed($Level); $Report .= get_Report_Problems("High", $Level).get_Report_Problems("Medium", $Level).get_Report_Problems("Low", $Level).get_Report_Problems("Safe", $Level); $Report .= get_SourceInfo()."



\n"; $Report .= getReportFooter($TargetLibraryFullName); $Report .= "\n
\n"; return $Report; } } sub getReportFooter($) { my $LibName = $_[0]; my $FooterStyle = (not $JoinReport)?"width:99%":"width:97%;padding-top:3px"; my $Footer = "
Generated on ".(localtime time); # report date $Footer .= " for $LibName"; # tested library/system name $Footer .= " by Java API Compliance Checker"; # tool name my $ToolSummary = "
A tool for checking backward compatibility of a Java library API  "; $Footer .= " $TOOL_VERSION  $ToolSummary
"; # tool version return $Footer; } sub get_Report_Problems($$) { my ($Priority, $Level) = @_; my $Report = get_Report_TypeProblems($Priority, $Level); if(my $MProblems = get_Report_MethodProblems($Priority, $Level)) { $Report .= $MProblems; } if($Priority eq "Low") { if($CheckImpl and $Level eq "Binary") { $Report .= get_Report_Implementation(); } } if($Report) { if($JoinReport) { if($Priority eq "Safe") { $Report = "".$Report; } else { $Report = "".$Report; } } else { if($Priority eq "Safe") { $Report = "".$Report; } else { $Report = "".$Report; } } } return $Report; } sub composeHTML_Head($$$$$) { my ($Title, $Keywords, $Description, $Styles, $Scripts) = @_; return " $Title "; } sub insertIDs($) { my $Text = $_[0]; while($Text=~/CONTENT_ID/) { if(int($Content_Counter)%2) { $ContentID -= 1; } $Text=~s/CONTENT_ID/c_$ContentID/; $ContentID += 1; $Content_Counter += 1; } return $Text; } sub readArchives($) { my $LibVersion = $_[0]; my @ArchivePaths = getArchives($LibVersion); if($#ArchivePaths==-1) { exitStatus("Error", "Java ARchives are not found in ".$Descriptor{$LibVersion}{"Version"}); } printMsg("INFO", "reading classes ".$Descriptor{$LibVersion}{"Version"}." ..."); $TypeID = 0; foreach my $ArchivePath (sort {length($a)<=>length($b)} @ArchivePaths) { readArchive($LibVersion, $ArchivePath); } foreach my $TName (keys(%{$TName_Tid{$LibVersion}})) { my $Tid = $TName_Tid{$LibVersion}{$TName}; if(not $TypeInfo{$LibVersion}{$Tid}{"Type"}) { if($TName=~/\A(void|boolean|char|byte|short|int|float|long|double)\Z/) { $TypeInfo{$LibVersion}{$Tid}{"Type"} = "primitive"; } else { $TypeInfo{$LibVersion}{$Tid}{"Type"} = "class"; } } } foreach my $Method (keys(%{$MethodInfo{$LibVersion}})) { $MethodInfo{$LibVersion}{$Method}{"Signature"} = get_Signature($Method, $LibVersion, "Full"); $tr_name{$Method} = get_TypeName($MethodInfo{$LibVersion}{$Method}{"Class"}, $LibVersion).".".get_Signature($Method, $LibVersion, "Short"); } } sub testSystem() { printMsg("INFO", "\nverifying detectable Java library changes"); my $LibName = "libsample_java"; rmtree($LibName); my $PackageName = "TestPackage"; my $Path_v1 = "$LibName/$PackageName.v1/$PackageName"; mkpath($Path_v1); my $Path_v2 = "$LibName/$PackageName.v2/$PackageName"; mkpath($Path_v2); my $TestsPath = "$LibName/Tests"; mkpath($TestsPath); # FirstCheckedException my $FirstCheckedException = "package $PackageName; public class FirstCheckedException extends Exception { }"; writeFile($Path_v1."/FirstCheckedException.java", $FirstCheckedException); writeFile($Path_v2."/FirstCheckedException.java", $FirstCheckedException); # SecondCheckedException my $SecondCheckedException = "package $PackageName; public class SecondCheckedException extends Exception { }"; writeFile($Path_v1."/SecondCheckedException.java", $SecondCheckedException); writeFile($Path_v2."/SecondCheckedException.java", $SecondCheckedException); # FirstUncheckedException my $FirstUncheckedException = "package $PackageName; public class FirstUncheckedException extends RuntimeException { }"; writeFile($Path_v1."/FirstUncheckedException.java", $FirstUncheckedException); writeFile($Path_v2."/FirstUncheckedException.java", $FirstUncheckedException); # SecondUncheckedException my $SecondUncheckedException = "package $PackageName; public class SecondUncheckedException extends RuntimeException { }"; writeFile($Path_v1."/SecondUncheckedException.java", $SecondUncheckedException); writeFile($Path_v2."/SecondUncheckedException.java", $SecondUncheckedException); # BaseAbstractClass my $BaseAbstractClass = "package $PackageName; public abstract class BaseAbstractClass { public Integer field; public Integer someMethod(Integer param) { return param; } public abstract Integer abstractMethod(Integer param); }"; writeFile($Path_v1."/BaseAbstractClass.java", $BaseAbstractClass); writeFile($Path_v2."/BaseAbstractClass.java", $BaseAbstractClass); # BaseClass my $BaseClass = "package $PackageName; public class BaseClass { public Integer field; public Integer method(Integer param) { return param; } }"; writeFile($Path_v1."/BaseClass.java", $BaseClass); writeFile($Path_v2."/BaseClass.java", $BaseClass); # BaseClass2 my $BaseClass2 = "package $PackageName; public class BaseClass2 { public Integer field2; public Integer method2(Integer param) { return param; } }"; writeFile($Path_v1."/BaseClass2.java", $BaseClass2); writeFile($Path_v2."/BaseClass2.java", $BaseClass2); # BaseInterface my $BaseInterface = "package $PackageName; public interface BaseInterface { public Integer field = 100; public Integer method(Integer param); }"; writeFile($Path_v1."/BaseInterface.java", $BaseInterface); writeFile($Path_v2."/BaseInterface.java", $BaseInterface); # BaseInterface2 my $BaseInterface2 = "package $PackageName; public interface BaseInterface2 { public Integer field2 = 100; public Integer method2(Integer param); }"; writeFile($Path_v1."/BaseInterface2.java", $BaseInterface2); writeFile($Path_v2."/BaseInterface2.java", $BaseInterface2); # BaseConstantInterface my $BaseConstantInterface = "package $PackageName; public interface BaseConstantInterface { public Integer CONSTANT = 10; public Integer CONSTANT2 = 100; }"; writeFile($Path_v1."/BaseConstantInterface.java", $BaseConstantInterface); writeFile($Path_v2."/BaseConstantInterface.java", $BaseConstantInterface); # Abstract_Method_Added_Checked_Exception writeFile($Path_v1."/AbstractMethodAddedCheckedException.java", "package $PackageName; public abstract class AbstractMethodAddedCheckedException { public abstract Integer someMethod() throws FirstCheckedException; }"); writeFile($Path_v2."/AbstractMethodAddedCheckedException.java", "package $PackageName; public abstract class AbstractMethodAddedCheckedException { public abstract Integer someMethod() throws FirstCheckedException, SecondCheckedException; }"); # Abstract_Method_Removed_Checked_Exception writeFile($Path_v1."/AbstractMethodRemovedCheckedException.java", "package $PackageName; public abstract class AbstractMethodRemovedCheckedException { public abstract Integer someMethod() throws FirstCheckedException, SecondCheckedException; }"); writeFile($Path_v2."/AbstractMethodRemovedCheckedException.java", "package $PackageName; public abstract class AbstractMethodRemovedCheckedException { public abstract Integer someMethod() throws FirstCheckedException; }"); # NonAbstract_Method_Added_Checked_Exception writeFile($Path_v1."/NonAbstractMethodAddedCheckedException.java", "package $PackageName; public class NonAbstractMethodAddedCheckedException { public Integer someMethod() throws FirstCheckedException { return 10; } }"); writeFile($Path_v2."/NonAbstractMethodAddedCheckedException.java", "package $PackageName; public class NonAbstractMethodAddedCheckedException { public Integer someMethod() throws FirstCheckedException, SecondCheckedException { return 10; } }"); # NonAbstract_Method_Removed_Checked_Exception writeFile($Path_v1."/NonAbstractMethodRemovedCheckedException.java", "package $PackageName; public class NonAbstractMethodRemovedCheckedException { public Integer someMethod() throws FirstCheckedException, SecondCheckedException { return 10; } }"); writeFile($Path_v2."/NonAbstractMethodRemovedCheckedException.java", "package $PackageName; public class NonAbstractMethodRemovedCheckedException { public Integer someMethod() throws FirstCheckedException { return 10; } }"); # Added_Unchecked_Exception writeFile($Path_v1."/AddedUncheckedException.java", "package $PackageName; public class AddedUncheckedException { public Integer someMethod() throws FirstUncheckedException { return 10; } }"); writeFile($Path_v2."/AddedUncheckedException.java", "package $PackageName; public class AddedUncheckedException { public Integer someMethod() throws FirstUncheckedException, SecondUncheckedException, NullPointerException { return 10; } }"); # Removed_Unchecked_Exception writeFile($Path_v1."/RemovedUncheckedException.java", "package $PackageName; public class RemovedUncheckedException { public Integer someMethod() throws FirstUncheckedException, SecondUncheckedException, NullPointerException { return 10; } }"); writeFile($Path_v2."/RemovedUncheckedException.java", "package $PackageName; public class RemovedUncheckedException { public Integer someMethod() throws FirstUncheckedException { return 10; } }"); # Changed_Method_Return_From_Void writeFile($Path_v1."/ChangedMethodReturnFromVoid.java", "package $PackageName; public class ChangedMethodReturnFromVoid { public void changedMethod(Integer param1, String[] param2) { } }"); writeFile($Path_v2."/ChangedMethodReturnFromVoid.java", "package $PackageName; public class ChangedMethodReturnFromVoid { public Integer changedMethod(Integer param1, String[] param2){ return param1; } }"); # Added_Method writeFile($Path_v1."/AddedMethod.java", "package $PackageName; public class AddedMethod { public Integer field = 100; }"); writeFile($Path_v2."/AddedMethod.java", "package $PackageName; public class AddedMethod { public Integer field = 100; public Integer addedMethod(Integer param1, String[] param2) { return param1; } public static String[] addedStaticMethod(String[] param) { return param; } }"); # Added_Method (Constructor) writeFile($Path_v1."/AddedConstructor.java", "package $PackageName; public class AddedConstructor { public Integer field = 100; }"); writeFile($Path_v2."/AddedConstructor.java", "package $PackageName; public class AddedConstructor { public Integer field = 100; public AddedConstructor() { } public AddedConstructor(Integer x, String y) { } }"); # Class_Added_Field writeFile($Path_v1."/ClassAddedField.java", "package $PackageName; public class ClassAddedField { public Integer otherField; }"); writeFile($Path_v2."/ClassAddedField.java", "package $PackageName; public class ClassAddedField { public Integer addedField; public Integer otherField; }"); # Interface_Added_Field writeFile($Path_v1."/InterfaceAddedField.java", "package $PackageName; public interface InterfaceAddedField { public Integer method(); }"); writeFile($Path_v2."/InterfaceAddedField.java", "package $PackageName; public interface InterfaceAddedField { public Integer addedField = 100; public Integer method(); }"); # Removed_NonConstant_Field (Class) writeFile($Path_v1."/ClassRemovedField.java", "package $PackageName; public class ClassRemovedField { public Integer removedField; public Integer otherField; }"); writeFile($Path_v2."/ClassRemovedField.java", "package $PackageName; public class ClassRemovedField { public Integer otherField; }"); writeFile($TestsPath."/Test_ClassRemovedField.java", "import $PackageName.*; public class Test_ClassRemovedField { public static void main(String[] args) { ClassRemovedField X = new ClassRemovedField(); Integer Copy = X.removedField; } }"); # Removed_Constant_Field (Interface) writeFile($Path_v1."/InterfaceRemovedConstantField.java", "package $PackageName; public interface InterfaceRemovedConstantField { public String someMethod(); public int removedField_Int = 1000; public String removedField_Str = \"Value\"; }"); writeFile($Path_v2."/InterfaceRemovedConstantField.java", "package $PackageName; public interface InterfaceRemovedConstantField { public String someMethod(); }"); # Removed_NonConstant_Field (Interface) writeFile($Path_v1."/InterfaceRemovedField.java", "package $PackageName; public interface InterfaceRemovedField { public String someMethod(); public BaseClass removedField = new BaseClass(); }"); writeFile($Path_v2."/InterfaceRemovedField.java", "package $PackageName; public interface InterfaceRemovedField { public String someMethod(); }"); # Renamed_Field writeFile($Path_v1."/RenamedField.java", "package $PackageName; public class RenamedField { public String oldName; }"); writeFile($Path_v2."/RenamedField.java", "package $PackageName; public class RenamedField { public String newName; }"); # Renamed_Constant_Field writeFile($Path_v1."/RenamedConstantField.java", "package $PackageName; public class RenamedConstantField { public final String oldName = \"Value\"; }"); writeFile($Path_v2."/RenamedConstantField.java", "package $PackageName; public class RenamedConstantField { public final String newName = \"Value\"; }"); # Changed_Field_Type writeFile($Path_v1."/ChangedFieldType.java", "package $PackageName; public class ChangedFieldType { public String fieldName; }"); writeFile($Path_v2."/ChangedFieldType.java", "package $PackageName; public class ChangedFieldType { public Integer fieldName; }"); # Changed_Field_Access writeFile($Path_v1."/ChangedFieldAccess.java", "package $PackageName; public class ChangedFieldAccess { public String fieldName; }"); writeFile($Path_v2."/ChangedFieldAccess.java", "package $PackageName; public class ChangedFieldAccess { private String fieldName; }"); # Changed_Final_Field_Value writeFile($Path_v1."/ChangedFinalFieldValue.java", "package $PackageName; public class ChangedFinalFieldValue { public final int field = 1; public final String field2 = \" \"; }"); writeFile($Path_v2."/ChangedFinalFieldValue.java", "package $PackageName; public class ChangedFinalFieldValue { public final int field = 2; public final String field2 = \"newValue\"; }"); # NonConstant_Field_Became_Static writeFile($Path_v1."/NonConstantFieldBecameStatic.java", "package $PackageName; public class NonConstantFieldBecameStatic { public String fieldName; }"); writeFile($Path_v2."/NonConstantFieldBecameStatic.java", "package $PackageName; public class NonConstantFieldBecameStatic { public static String fieldName; }"); # NonConstant_Field_Became_NonStatic writeFile($Path_v1."/NonConstantFieldBecameNonStatic.java", "package $PackageName; public class NonConstantFieldBecameNonStatic { public static String fieldName; }"); writeFile($Path_v2."/NonConstantFieldBecameNonStatic.java", "package $PackageName; public class NonConstantFieldBecameNonStatic { public String fieldName; }"); # Constant_Field_Became_NonStatic writeFile($Path_v1."/ConstantFieldBecameNonStatic.java", "package $PackageName; public class ConstantFieldBecameNonStatic { public final static String fieldName = \"Value\"; }"); writeFile($Path_v2."/ConstantFieldBecameNonStatic.java", "package $PackageName; public class ConstantFieldBecameNonStatic { public final String fieldName = \"Value\"; }"); # Field_Became_Final writeFile($Path_v1."/FieldBecameFinal.java", "package $PackageName; public class FieldBecameFinal { public String fieldName; }"); writeFile($Path_v2."/FieldBecameFinal.java", "package $PackageName; public class FieldBecameFinal { public final String fieldName = \"Value\"; }"); # Field_Became_NonFinal writeFile($Path_v1."/FieldBecameNonFinal.java", "package $PackageName; public class FieldBecameNonFinal { public final String fieldName = \"Value\"; }"); writeFile($Path_v2."/FieldBecameNonFinal.java", "package $PackageName; public class FieldBecameNonFinal { public String fieldName; }"); # Removed_Method writeFile($Path_v1."/RemovedMethod.java", "package $PackageName; public class RemovedMethod { public Integer field = 100; public Integer removedMethod(Integer param1, String param2) { return param1; } public static Integer removedStaticMethod(Integer param) { return param; } }"); writeFile($Path_v2."/RemovedMethod.java", "package $PackageName; public class RemovedMethod { public Integer field = 100; }"); # Removed_Method (Deprecated) writeFile($Path_v1."/RemovedDeprecatedMethod.java", "package $PackageName; public class RemovedDeprecatedMethod { public Integer field = 100; public Integer otherMethod(Integer param) { return param; } \@Deprecated public Integer removedMethod(Integer param1, String param2) { return param1; } }"); writeFile($Path_v2."/RemovedDeprecatedMethod.java", "package $PackageName; public class RemovedDeprecatedMethod { public Integer field = 100; public Integer otherMethod(Integer param) { return param; } }"); # Interface_Removed_Abstract_Method writeFile($Path_v1."/InterfaceRemovedAbstractMethod.java", "package $PackageName; public interface InterfaceRemovedAbstractMethod extends BaseInterface, BaseInterface2 { public void removedMethod(Integer param1, java.io.ObjectOutput param2); public void someMethod(Integer param); }"); writeFile($Path_v2."/InterfaceRemovedAbstractMethod.java", "package $PackageName; public interface InterfaceRemovedAbstractMethod extends BaseInterface, BaseInterface2 { public void someMethod(Integer param); }"); # Interface_Added_Abstract_Method writeFile($Path_v1."/InterfaceAddedAbstractMethod.java", "package $PackageName; public interface InterfaceAddedAbstractMethod extends BaseInterface, BaseInterface2 { public void someMethod(Integer param); }"); writeFile($Path_v2."/InterfaceAddedAbstractMethod.java", "package $PackageName; public interface InterfaceAddedAbstractMethod extends BaseInterface, BaseInterface2 { public void someMethod(Integer param); public Integer addedMethod(Integer param); }"); # Variable_Arity_To_Array writeFile($Path_v1."/VariableArityToArray.java", "package $PackageName; public class VariableArityToArray { public void someMethod(Integer x, String... y) { }; }"); writeFile($Path_v2."/VariableArityToArray.java", "package $PackageName; public class VariableArityToArray { public void someMethod(Integer x, String[] y) { }; }"); # Class_Became_Interface writeFile($Path_v1."/ClassBecameInterface.java", "package $PackageName; public class ClassBecameInterface extends BaseClass { public Integer someMethod(Integer param) { return param; } }"); writeFile($Path_v2."/ClassBecameInterface.java", "package $PackageName; public interface ClassBecameInterface extends BaseInterface, BaseInterface2 { public Integer someMethod(Integer param); }"); # Added_Super_Class writeFile($Path_v1."/AddedSuperClass.java", "package $PackageName; public class AddedSuperClass { public Integer someMethod(Integer param) { return param; } }"); writeFile($Path_v2."/AddedSuperClass.java", "package $PackageName; public class AddedSuperClass extends BaseClass { public Integer someMethod(Integer param) { return param; } }"); # Abstract_Class_Added_Super_Abstract_Class writeFile($Path_v1."/AbstractClassAddedSuperAbstractClass.java", "package $PackageName; public abstract class AbstractClassAddedSuperAbstractClass { public Integer someMethod(Integer param) { return param; } }"); writeFile($Path_v2."/AbstractClassAddedSuperAbstractClass.java", "package $PackageName; public abstract class AbstractClassAddedSuperAbstractClass extends BaseAbstractClass { public Integer someMethod(Integer param) { return param; } }"); # Removed_Super_Class writeFile($Path_v1."/RemovedSuperClass.java", "package $PackageName; public class RemovedSuperClass extends BaseClass { public Integer someMethod(Integer param) { return param; } }"); writeFile($Path_v2."/RemovedSuperClass.java", "package $PackageName; public class RemovedSuperClass { public Integer someMethod(Integer param) { return param; } }"); # Changed_Super_Class writeFile($Path_v1."/ChangedSuperClass.java", "package $PackageName; public class ChangedSuperClass extends BaseClass { public Integer someMethod(Integer param) { return param; } }"); writeFile($Path_v2."/ChangedSuperClass.java", "package $PackageName; public class ChangedSuperClass extends BaseClass2 { public Integer someMethod(Integer param) { return param; } }"); # Abstract_Class_Added_Super_Interface writeFile($Path_v1."/AbstractClassAddedSuperInterface.java", "package $PackageName; public abstract class AbstractClassAddedSuperInterface implements BaseInterface { public Integer method(Integer param) { return param; } }"); writeFile($Path_v2."/AbstractClassAddedSuperInterface.java", "package $PackageName; public abstract class AbstractClassAddedSuperInterface implements BaseInterface, BaseInterface2 { public Integer method(Integer param) { return param; } }"); # Class_Removed_Super_Interface writeFile($Path_v1."/ClassRemovedSuperInterface.java", "package $PackageName; public class ClassRemovedSuperInterface implements BaseInterface, BaseInterface2 { public Integer method(Integer param) { return param; } public Integer method2(Integer param) { return param; } }"); writeFile($Path_v2."/ClassRemovedSuperInterface.java", "package $PackageName; public class ClassRemovedSuperInterface implements BaseInterface { public Integer method(Integer param) { return param; } public Integer method2(Integer param) { return param; } }"); # Interface_Added_Super_Interface writeFile($Path_v1."/InterfaceAddedSuperInterface.java", "package $PackageName; public interface InterfaceAddedSuperInterface extends BaseInterface { public Integer someMethod(Integer param); }"); writeFile($Path_v2."/InterfaceAddedSuperInterface.java", "package $PackageName; public interface InterfaceAddedSuperInterface extends BaseInterface, BaseInterface2 { public Integer someMethod(Integer param); }"); # Interface_Added_Super_Constant_Interface writeFile($Path_v1."/InterfaceAddedSuperConstantInterface.java", "package $PackageName; public interface InterfaceAddedSuperConstantInterface extends BaseInterface { public Integer someMethod(Integer param); }"); writeFile($Path_v2."/InterfaceAddedSuperConstantInterface.java", "package $PackageName; public interface InterfaceAddedSuperConstantInterface extends BaseInterface, BaseConstantInterface { public Integer someMethod(Integer param); }"); # Interface_Removed_Super_Interface writeFile($Path_v1."/InterfaceRemovedSuperInterface.java", "package $PackageName; public interface InterfaceRemovedSuperInterface extends BaseInterface, BaseInterface2 { public Integer someMethod(Integer param); }"); writeFile($Path_v2."/InterfaceRemovedSuperInterface.java", "package $PackageName; public interface InterfaceRemovedSuperInterface extends BaseInterface { public Integer someMethod(Integer param); }"); # Interface_Removed_Super_Constant_Interface writeFile($Path_v1."/InterfaceRemovedSuperConstantInterface.java", "package $PackageName; public interface InterfaceRemovedSuperConstantInterface extends BaseInterface, BaseConstantInterface { public Integer someMethod(Integer param); }"); writeFile($Path_v2."/InterfaceRemovedSuperConstantInterface.java", "package $PackageName; public interface InterfaceRemovedSuperConstantInterface extends BaseInterface { public Integer someMethod(Integer param); }"); # Interface_Became_Class writeFile($Path_v1."/InterfaceBecameClass.java", "package $PackageName; public interface InterfaceBecameClass extends BaseInterface, BaseInterface2 { public Integer someMethod(Integer param); }"); writeFile($Path_v2."/InterfaceBecameClass.java", "package $PackageName; public class InterfaceBecameClass extends BaseClass { public Integer someMethod(Integer param) { return param; } }"); # Removed_Class writeFile($Path_v1."/RemovedClass.java", "package $PackageName; public class RemovedClass extends BaseClass { public Integer someMethod(Integer param){ return param; } }"); # Removed_Class (Deprecated) writeFile($Path_v1."/RemovedDeprecatedClass.java", "package $PackageName; \@Deprecated public class RemovedDeprecatedClass { public Integer someMethod(Integer param){ return param; } }"); # Removed_Interface writeFile($Path_v1."/RemovedInterface.java", "package $PackageName; public interface RemovedInterface extends BaseInterface, BaseInterface2 { public Integer someMethod(Integer param); }"); # NonAbstract_Class_Added_Abstract_Method writeFile($Path_v1."/NonAbstractClassAddedAbstractMethod.java", "package $PackageName; public class NonAbstractClassAddedAbstractMethod { public Integer someMethod(Integer param1, String[] param2) { return param1; }; }"); writeFile($Path_v2."/NonAbstractClassAddedAbstractMethod.java", "package $PackageName; public abstract class NonAbstractClassAddedAbstractMethod { public Integer someMethod(Integer param1, String[] param2) { return param1; }; public abstract Integer addedMethod(Integer param); }"); # Abstract_Class_Added_Abstract_Method writeFile($Path_v1."/AbstractClassAddedAbstractMethod.java", "package $PackageName; public abstract class AbstractClassAddedAbstractMethod { public Integer someMethod(Integer param1, String[] param2) { return param1; }; }"); writeFile($Path_v2."/AbstractClassAddedAbstractMethod.java", "package $PackageName; public abstract class AbstractClassAddedAbstractMethod { public Integer someMethod(Integer param1, String[] param2) { return param1; }; public abstract Integer addedMethod(Integer param); }"); # Class_Became_Abstract writeFile($Path_v1."/ClassBecameAbstract.java", "package $PackageName; public class ClassBecameAbstract { public Integer someMethod(Integer param1, String[] param2) { return param1; }; }"); writeFile($Path_v2."/ClassBecameAbstract.java", "package $PackageName; public abstract class ClassBecameAbstract { public Integer someMethod(Integer param1, String[] param2) { return param1; }; public abstract Integer addedMethod(Integer param); }"); # Class_Became_Final writeFile($Path_v1."/ClassBecameFinal.java", "package $PackageName; public class ClassBecameFinal { public Integer someMethod(Integer param1, String[] param2) { return param1; }; }"); writeFile($Path_v2."/ClassBecameFinal.java", "package $PackageName; public final class ClassBecameFinal { public Integer someMethod(Integer param1, String[] param2) { return param1; }; }"); # Class_Removed_Abstract_Method writeFile($Path_v1."/ClassRemovedAbstractMethod.java", "package $PackageName; public abstract class ClassRemovedAbstractMethod { public Integer someMethod(Integer param1, String[] param2) { return param1; }; public abstract Integer removedMethod(Integer param); }"); writeFile($Path_v2."/ClassRemovedAbstractMethod.java", "package $PackageName; public abstract class ClassRemovedAbstractMethod { public Integer someMethod(Integer param1, String[] param2) { return param1; }; }"); # Class_Method_Became_Abstract writeFile($Path_v1."/ClassMethodBecameAbstract.java", "package $PackageName; public abstract class ClassMethodBecameAbstract { public Integer someMethod(Integer param1, String[] param2) { return param1; }; public Integer someMethod(Integer param){ return param; }; }"); writeFile($Path_v2."/ClassMethodBecameAbstract.java", "package $PackageName; public abstract class ClassMethodBecameAbstract { public Integer someMethod(Integer param1, String[] param2) { return param1; }; public abstract Integer someMethod(Integer param); }"); # Class_Method_Became_NonAbstract writeFile($Path_v1."/ClassMethodBecameNonAbstract.java", "package $PackageName; public abstract class ClassMethodBecameNonAbstract { public Integer someMethod(Integer param1, String[] param2) { return param1; }; public abstract Integer someMethod(Integer param); }"); writeFile($Path_v2."/ClassMethodBecameNonAbstract.java", "package $PackageName; public abstract class ClassMethodBecameNonAbstract { public Integer someMethod(Integer param1, String[] param2) { return param1; }; public Integer someMethod(Integer param){ return param; }; }"); # Method_Became_Static writeFile($Path_v1."/MethodBecameStatic.java", "package $PackageName; public class MethodBecameStatic { public Integer someMethod(Integer param) { return param; }; }"); writeFile($Path_v2."/MethodBecameStatic.java", "package $PackageName; public class MethodBecameStatic { public static Integer someMethod(Integer param) { return param; }; }"); # Method_Became_NonStatic writeFile($Path_v1."/MethodBecameNonStatic.java", "package $PackageName; public class MethodBecameNonStatic { public static Integer someMethod(Integer param) { return param; }; }"); writeFile($Path_v2."/MethodBecameNonStatic.java", "package $PackageName; public class MethodBecameNonStatic { public Integer someMethod(Integer param) { return param; }; }"); # Static_Method_Became_Final writeFile($Path_v1."/StaticMethodBecameFinal.java", "package $PackageName; public class StaticMethodBecameFinal { public static Integer someMethod(Integer param) { return param; }; }"); writeFile($Path_v2."/StaticMethodBecameFinal.java", "package $PackageName; public class StaticMethodBecameFinal { public static final Integer someMethod(Integer param) { return param; }; }"); # NonStatic_Method_Became_Final writeFile($Path_v1."/NonStaticMethodBecameFinal.java", "package $PackageName; public class NonStaticMethodBecameFinal { public Integer someMethod(Integer param) { return param; }; }"); writeFile($Path_v2."/NonStaticMethodBecameFinal.java", "package $PackageName; public class NonStaticMethodBecameFinal { public final Integer someMethod(Integer param) { return param; }; }"); # Method_Became_Abstract writeFile($Path_v1."/MethodBecameAbstract.java", "package $PackageName; public abstract class MethodBecameAbstract { public Integer someMethod(Integer param) { return param; }; }"); writeFile($Path_v2."/MethodBecameAbstract.java", "package $PackageName; public abstract class MethodBecameAbstract { public abstract Integer someMethod(Integer param); }"); # Method_Became_NonAbstract writeFile($Path_v1."/MethodBecameNonAbstract.java", "package $PackageName; public abstract class MethodBecameNonAbstract { public abstract Integer someMethod(Integer param); }"); writeFile($Path_v2."/MethodBecameNonAbstract.java", "package $PackageName; public abstract class MethodBecameNonAbstract { public Integer someMethod(Integer param) { return param; }; }"); # Changed_Method_Access writeFile($Path_v1."/ChangedMethodAccess.java", "package $PackageName; public class ChangedMethodAccess { public Integer someMethod(Integer param) { return param; }; }"); writeFile($Path_v2."/ChangedMethodAccess.java", "package $PackageName; public class ChangedMethodAccess { protected Integer someMethod(Integer param) { return param; }; }"); # Method_Became_Synchronized writeFile($Path_v1."/MethodBecameSynchronized.java", "package $PackageName; public class MethodBecameSynchronized { public Integer someMethod(Integer param) { return param; }; }"); writeFile($Path_v2."/MethodBecameSynchronized.java", "package $PackageName; public class MethodBecameSynchronized { public synchronized Integer someMethod(Integer param) { return param; }; }"); # Method_Became_NonSynchronized writeFile($Path_v1."/MethodBecameNonSynchronized.java", "package $PackageName; public class MethodBecameNonSynchronized { public synchronized Integer someMethod(Integer param) { return param; }; }"); writeFile($Path_v2."/MethodBecameNonSynchronized.java", "package $PackageName; public class MethodBecameNonSynchronized { public Integer someMethod(Integer param) { return param; }; }"); # Class_Overridden_Method writeFile($Path_v1."/OverriddenMethod.java", "package $PackageName; public class OverriddenMethod extends BaseClass { public Integer someMethod(Integer param) { return param; } }"); writeFile($Path_v2."/OverriddenMethod.java", "package $PackageName; public class OverriddenMethod extends BaseClass { public Integer someMethod(Integer param) { return param; } public Integer method(Integer param) { return 2*param; } }"); # Class_Method_Moved_Up_Hierarchy writeFile($Path_v1."/ClassMethodMovedUpHierarchy.java", "package $PackageName; public class ClassMethodMovedUpHierarchy extends BaseClass { public Integer someMethod(Integer param) { return param; } public Integer method(Integer param) { return 2*param; } }"); writeFile($Path_v2."/ClassMethodMovedUpHierarchy.java", "package $PackageName; public class ClassMethodMovedUpHierarchy extends BaseClass { public Integer someMethod(Integer param) { return param; } }"); # Class_Method_Moved_Up_Hierarchy (Interface Method) - should not be reported writeFile($Path_v1."/InterfaceMethodMovedUpHierarchy.java", "package $PackageName; public interface InterfaceMethodMovedUpHierarchy extends BaseInterface { public Integer method(Integer param); public Integer method2(Integer param); }"); writeFile($Path_v2."/InterfaceMethodMovedUpHierarchy.java", "package $PackageName; public interface InterfaceMethodMovedUpHierarchy extends BaseInterface { public Integer method2(Integer param); }"); # Class_Method_Moved_Up_Hierarchy (Abstract Method) - should not be reported writeFile($Path_v1."/AbstractMethodMovedUpHierarchy.java", "package $PackageName; public abstract class AbstractMethodMovedUpHierarchy implements BaseInterface { public abstract Integer method(Integer param); public abstract Integer method2(Integer param); }"); writeFile($Path_v2."/AbstractMethodMovedUpHierarchy.java", "package $PackageName; public abstract class AbstractMethodMovedUpHierarchy implements BaseInterface { public abstract Integer method2(Integer param); }"); # Use writeFile($Path_v1."/Use.java", "package $PackageName; public class Use { public FieldBecameFinal field; public void someMethod(FieldBecameFinal[] param) { }; public void someMethod(Use param) { }; public Integer someMethod(AbstractClassAddedSuperAbstractClass param) { return 0; } public Integer someMethod(AbstractClassAddedAbstractMethod param) { return 0; } public Integer someMethod(InterfaceAddedAbstractMethod param) { return 0; } public Integer someMethod(InterfaceAddedSuperInterface param) { return 0; } public Integer someMethod(AbstractClassAddedSuperInterface param) { return 0; } }"); writeFile($Path_v2."/Use.java", "package $PackageName; public class Use { public FieldBecameFinal field; public void someMethod(FieldBecameFinal[] param) { }; public void someMethod(Use param) { }; public Integer someMethod(AbstractClassAddedSuperAbstractClass param) { return param.abstractMethod(100)+param.field; } public Integer someMethod(AbstractClassAddedAbstractMethod param) { return param.addedMethod(100); } public Integer someMethod(InterfaceAddedAbstractMethod param) { return param.addedMethod(100); } public Integer someMethod(InterfaceAddedSuperInterface param) { return param.method2(100); } public Integer someMethod(AbstractClassAddedSuperInterface param) { return param.method2(100); } }"); # Added_Package writeFile($Path_v2."/AddedPackage/AddedPackageClass.java", "package $PackageName.AddedPackage; public class AddedPackageClass { public Integer field; public void someMethod(Integer param) { }; }"); # Removed_Package writeFile($Path_v1."/RemovedPackage/RemovedPackageClass.java", "package $PackageName.RemovedPackage; public class RemovedPackageClass { public Integer field; public void someMethod(Integer param) { }; }"); my $BuildRoot1 = get_dirname($Path_v1); my $BuildRoot2 = get_dirname($Path_v2); if(compileJavaLib($LibName, $BuildRoot1, $BuildRoot2)) { runTests($TestsPath, $PackageName, $BuildRoot1, $BuildRoot2); runChecker($LibName, $BuildRoot1, $BuildRoot2); } } sub readArchive($$) { my ($LibVersion, $Path) = @_; return if(not $Path or not -e $Path); my $ArchiveName = get_filename($Path); $LibArchives{$LibVersion}{$ArchiveName} = 1; $Path = get_abs_path($Path); my $JarCmd = get_CmdPath("jar"); if(not $JarCmd) { exitStatus("Not_Found", "can't find \"jar\" command"); } my $ExtractPath = "$TMP_DIR/".($ExtractCounter++); rmtree($ExtractPath); mkpath($ExtractPath); chdir($ExtractPath); system($JarCmd." -xf \"$Path\""); if($?) { exitStatus("Error", "can't extract \'$Path\'"); } chdir($ORIG_DIR); my @Classes = (); foreach my $ClassPath (cmd_find($ExtractPath,"","*\.class","")) { $ClassPath=~s/\.class\Z//g; my $ClassName = get_filename($ClassPath); next if($ClassName=~/\$\d/); my $RelPath = cut_path_prefix(get_dirname($ClassPath), $ExtractPath); $ClassPath = cut_path_prefix($ClassPath, $TMP_DIR); if($RelPath=~/\./) { # jaxb-osgi.jar/1.0/org/apache next; } my $Package = get_PFormat($RelPath); if(skip_package($Package, $LibVersion)) { # internal packages next; } $ClassName=~s/\$/./g;# real name for GlyphView$GlyphPainter is GlyphView.GlyphPainter $LibClasses{$LibVersion}{$ClassName} = $Package; # Javap decompiler accepts relative paths only push(@Classes, $ClassPath); } if($#Classes!=-1) { foreach my $PartRef (divideArray(\@Classes)) { readClasses($PartRef, $LibVersion, get_filename($Path)); } } foreach my $SubArchive (cmd_find($ExtractPath,"","*\.jar","")) { # recursive step readArchive($LibVersion, $SubArchive); } } sub native_path($) { my $Path = $_[0]; if($OSgroup eq "windows") { $Path=~s/[\/\\]+/\\/g; } return $Path; } sub divideArray($) { my $ArrRef = $_[0]; return () if(not $ArrRef); my @Array = @{$ArrRef}; return () if($#{$ArrRef}==-1); my @Res = (); my $Sub = []; my $Len = 0; foreach my $Pos (0 .. $#{$ArrRef}) { my $Arg = $ArrRef->[$Pos]; my $Arg_L = length($Arg) + 1; # space if($Len < $ARG_MAX - 250) { push(@{$Sub}, $Arg); $Len += $Arg_L; } else { push(@Res, $Sub); $Sub = [$Arg]; $Len = $Arg_L; } } if($#{$Sub}!=-1) { push(@Res, $Sub); } return @Res; } sub readUsage_Client($) { my $Path = $_[0]; return if(not $Path or not -e $Path); my $JarCmd = get_CmdPath("jar"); if(not $JarCmd) { exitStatus("Not_Found", "can't find \"jar\" command"); } $Path = get_abs_path($Path); my $ExtractPath = "$TMP_DIR/extracted"; rmtree($ExtractPath); mkpath($ExtractPath); chdir($ExtractPath); system($JarCmd." -xf \"$Path\""); if($?) { exitStatus("Error", "can't extract \'$Path\'"); } chdir($ORIG_DIR); my @Classes = (); foreach my $ClassPath (cmd_find($ExtractPath,"","*\.class","")) { next if(get_filename($ClassPath)=~/\$/); $ClassPath=~s/\.class\Z//g; $ClassPath = cut_path_prefix($ClassPath, $ORIG_DIR); push(@Classes, $ClassPath); } readUsage_Classes(\@Classes); } sub readUsage_Classes($) { my $Paths = $_[0]; return () if(not $Paths); my $JavapCmd = get_CmdPath("javap"); if(not $JavapCmd) { exitStatus("Not_Found", "can't find \"javap\" command"); } my $Input = join(" ", @{$Paths}); open(CONTENT, "$JavapCmd -c -private $Input |"); while() { if(/\/\/(Method|InterfaceMethod)\s+(.+)\Z/) { $UsedMethods_Client{$2} = 1; } elsif(/\/\/Field\s+(.+)\Z/) { my $FieldName = $1; if(/\s+(putfield|getfield|getstatic|putstatic)\s+/) { $UsedFields_Client{$FieldName} = $1; } } } close(CONTENT); } sub registerType($$) { my ($TName, $LibVersion) = @_; return 0 if(not $TName); $TName=~s/#/./g; if($TName_Tid{$LibVersion}{$TName}) { return $TName_Tid{$LibVersion}{$TName}; } if(not $TName_Tid{$LibVersion}{$TName}) { if(my $ID = ++$TypeID) { $TName_Tid{$LibVersion}{$TName} = "$ID"; } } my $Tid = $TName_Tid{$LibVersion}{$TName}; $TypeInfo{$LibVersion}{$Tid}{"Name"} = $TName; if($TName=~/(.+)\[\]\Z/) { if(my $BaseTypeId = registerType($1, $LibVersion)) { $TypeInfo{$LibVersion}{$Tid}{"BaseType"} = $BaseTypeId; $TypeInfo{$LibVersion}{$Tid}{"Type"} = "array"; } } return $Tid; } sub readClasses($$$) { my ($Paths, $LibVersion, $ArchiveName) = @_; return if(not $Paths or not $LibVersion or not $ArchiveName); my $JavapCmd = get_CmdPath("javap"); if(not $JavapCmd) { exitStatus("Not_Found", "can't find \"javap\" command"); } my $Input = join(" ", @{$Paths}); if($OSgroup ne "windows") { # on unix ensure that the system does not try and interpret the $, by escaping it $Input=~s/\$/\\\$/g; } my $Output = $TMP_DIR."/class-dump.txt"; rmtree($Output); my $Cmd = "$JavapCmd -s -private"; if(not $Quick) { $Cmd .= " -c -verbose"; } chdir($TMP_DIR); system($Cmd." ".$Input." >\"$Output\" 2>\"$TMP_DIR/warn\""); chdir($ORIG_DIR); if(not -e $Output) { exitStatus("Error", "internal error in parser, try to reduce ARG_MAX"); } if($Debug) { appendFile($DEBUG_PATH{$LibVersion}."/class-dump.txt", readFile($Output)); } # ! private info should be processed open(CONTENT, "$TMP_DIR/class-dump.txt"); my @Content = ; close(CONTENT); my (%TypeAttr, $CurrentMethod, $CurrentPackage, $CurrentClass) = (); my ($InParamTable, $InExceptionTable, $InCode) = (0, 0); my ($ParamPos, $FieldPos, $LineNum) = (0, 0, 0); while($LineNum<=$#Content) { my $LINE = $Content[$LineNum++]; next if($LINE=~/\A\s*(?:const|#\d|AnnotationDefault|Compiled|Source|Constant|RuntimeVisibleAnnotations)/); next if($LINE=~/\sof\s|\sline \d+:|\[\s*class|= \[| \$|\$\d| class\$/); if(index($LINE, " $LINE=~s///; } $LINE=~s/\s*,\s*/,/g; $LINE=~s/\$/#/g; if(index($LINE, "LocalVariableTable")!=-1) { $InParamTable += 1; } elsif($LINE=~/Exception\s+table/) { $InExceptionTable = 1; } elsif($LINE=~/\A\s*Code:/) { $InCode += 1; } elsif($LINE=~/\A\s*\d+:\s*(.*)\Z/) { # read Code if($InCode==1) { if($CheckImpl) { $MethodBody{$LibVersion}{$CurrentMethod} .= "$1\n"; } if($LINE=~/\/\/\s*(Method|InterfaceMethod)\s+(.+)\Z/) { my $InvokedName = $2; if($LibVersion==2) { if(defined $MethodInfo{1}{$CurrentMethod}) { $MethodInvoked{2}{$InvokedName}{$CurrentMethod} = 1; } if(index($LINE, " invokestatic ")==-1 and index($InvokedName, "")==-1) { $InvokedName=~s/\A\"\[L(.+);"/$1/g; $InvokedName=~s/#/./g; # 3: invokevirtual #2; //Method "[Lcom/sleepycat/je/Database#DbState;".clone:()Ljava/lang/Object; if($InvokedName=~/\A(.+?)\./) { my $NClassName = $1; if($NClassName!~/\"/) { $NClassName=~s!/!.!g; $ClassMethod_AddedInvoked{$NClassName}{$InvokedName} = $CurrentMethod; } } } } else { $MethodInvoked{1}{$InvokedName}{$CurrentMethod} = 1; } } elsif($LibVersion==2 and defined $MethodInfo{1}{$CurrentMethod} and $LINE=~/\/\/Field\s+(.+)\Z/) { my $UsedFieldName = $1; $FieldUsed{$UsedFieldName}{$CurrentMethod} = 1; } } } elsif($CurrentMethod and $InParamTable==1 and $LINE=~/\A\s+0\s+\d+\s+\d+\s+(\w+)/) { # read parameter names from LocalVariableTable my $PName = $1; if($PName ne "this" and $PName=~/[a-z]/i) { if($CurrentMethod) { if(defined $MethodInfo{$LibVersion}{$CurrentMethod} and defined $MethodInfo{$LibVersion}{$CurrentMethod}{"Param"}{$ParamPos} and defined $MethodInfo{$LibVersion}{$CurrentMethod}{"Param"}{$ParamPos}{"Type"}) { $MethodInfo{$LibVersion}{$CurrentMethod}{"Param"}{$ParamPos}{"Name"} = $PName; $ParamPos++; } } } } elsif($CurrentClass and $LINE=~/(\A|\s+)([^\s]+)\s+([^\s]+)\s*\((.*)\)\s*(throws\s*([^\s]+)|)\s*;\Z/) { # attributes of methods and constructors my (%MethodAttr, $ParamsLine, $Exceptions) = (); $InParamTable = 0; # read the first local variable table $InCode = 0; # read the first code ($MethodAttr{"Return"}, $MethodAttr{"ShortName"}, $ParamsLine, $Exceptions) = ($2, $3, $4, $6); $MethodAttr{"ShortName"}=~s/#/./g; if($Exceptions) { foreach my $E (split(/,/, $Exceptions)) { $MethodAttr{"Exceptions"}{registerType($E, $LibVersion)} = 1; } } if($LINE=~/(\A|\s+)(public|protected|private)\s+/) { $MethodAttr{"Access"} = $2; } else { $MethodAttr{"Access"} = "package-private"; } $MethodAttr{"Class"} = registerType($TypeAttr{"Name"}, $LibVersion); if($MethodAttr{"ShortName"}=~/\A(|(.+)\.)\Q$CurrentClass\E\Z/) { if($2) { $MethodAttr{"Package"} = $2; $CurrentPackage = $MethodAttr{"Package"}; $MethodAttr{"ShortName"} = $CurrentClass; } $MethodAttr{"Constructor"} = 1; delete($MethodAttr{"Return"}); } else { my $ReturnName = $MethodAttr{"Return"}; $MethodAttr{"Return"} = registerType($ReturnName, $LibVersion); } $ParamPos = 0; foreach my $ParamTName (split(/\s*,\s*/, $ParamsLine)) { %{$MethodAttr{"Param"}{$ParamPos}} = ("Type"=>registerType($ParamTName, $LibVersion), "Name"=>"p".($ParamPos+1)); $ParamPos++; } $ParamPos = 0; if(not $MethodAttr{"Constructor"}) { # methods if($CurrentPackage) { $MethodAttr{"Package"} = $CurrentPackage; } if($LINE=~/(\A|\s+)abstract\s+/) { $MethodAttr{"Abstract"} = 1; } if($LINE=~/(\A|\s+)final\s+/) { $MethodAttr{"Final"} = 1; } if($LINE=~/(\A|\s+)static\s+/) { $MethodAttr{"Static"} = 1; } if($LINE=~/(\A|\s+)native\s+/) { $MethodAttr{"Native"} = 1; } if($LINE=~/(\A|\s+)synchronized\s+/) { $MethodAttr{"Synchronized"} = 1; } } # read the Signature if($Content[$LineNum++]=~/Signature:\s*(.+)\Z/i) { # create run-time unique name ( java/io/PrintStream.println (Ljava/lang/String;)V ) if($MethodAttr{"Constructor"}) { $CurrentMethod = $CurrentClass.".\"\":".$1; } else { $CurrentMethod = $CurrentClass.".".$MethodAttr{"ShortName"}.":".$1; } if(my $PackageName = get_SFormat($CurrentPackage)) { $CurrentMethod = $PackageName."/".$CurrentMethod; } } else { exitStatus("Error", "internal error - can't read method signature"); } $MethodAttr{"Archive"} = $ArchiveName; if($CurrentMethod) { %{$MethodInfo{$LibVersion}{$CurrentMethod}} = %MethodAttr; if($MethodAttr{"Access"}=~/public|protected/) { $Class_Methods{$LibVersion}{$TypeAttr{"Name"}}{$CurrentMethod} = 1; if($MethodAttr{"Abstract"}) { $Class_AbstractMethods{$LibVersion}{$TypeAttr{"Name"}}{$CurrentMethod} = 1; } } } } elsif($CurrentClass and $LINE=~/(\A|\s+)([^\s]+)\s+(\w+);\Z/) { # fields my ($TName, $FName) = ($2, $3); $TypeAttr{"Fields"}{$FName}{"Type"} = registerType($TName, $LibVersion); if($LINE=~/(\A|\s+)final\s+/) { $TypeAttr{"Fields"}{$FName}{"Final"} = 1; } if($LINE=~/(\A|\s+)static\s+/) { $TypeAttr{"Fields"}{$FName}{"Static"} = 1; } if($LINE=~/(\A|\s+)transient\s+/) { $TypeAttr{"Fields"}{$FName}{"Transient"} = 1; } if($LINE=~/(\A|\s+)volatile\s+/) { $TypeAttr{"Fields"}{$FName}{"Volatile"} = 1; } if($LINE=~/(\A|\s+)(public|protected|private)\s+/) { $TypeAttr{"Fields"}{$FName}{"Access"} = $2; } else { $TypeAttr{"Fields"}{$FName}{"Access"} = "package-private"; } if($TypeAttr{"Fields"}{$FName}{"Access"}!~/private/) { $Class_Fields{$LibVersion}{$TypeAttr{"Name"}}{$FName}=$TypeAttr{"Fields"}{$FName}{"Type"}; } $TypeAttr{"Fields"}{$FName}{"Pos"} = $FieldPos++; # read the Signature if($Content[$LineNum++]=~/Signature:\s*(.+)\Z/i) { my $FSignature = $1; if(my $PackageName = get_SFormat($CurrentPackage)) { $TypeAttr{"Fields"}{$FName}{"Mangled"} = $PackageName."/".$CurrentClass.".".$FName.":".$FSignature; } } if($Content[$LineNum]=~/flags:/i) { # flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL $LineNum++; } # read the Value if($Content[$LineNum]=~/Constant\s*value:\s*([^\s]+)\s(.*)\Z/i) { # Java 6: Constant value: ... # Java 7: ConstantValue: ... $LineNum+=1; my ($TName, $Value) = ($1, $2); if($Value) { if($Value=~s/Deprecated:\s*true\Z//g) { # deprecated values: ? } $TypeAttr{"Fields"}{$FName}{"Value"} = $Value; } elsif($TName eq "String") { $TypeAttr{"Fields"}{$FName}{"Value"} = "\@EMPTY_STRING\@"; } } } elsif($LINE=~/(\A|\s+)(class|interface)\s+([^\s\{]+)(\s+|\{|\Z)/) { # properties of classes and interfaces if($TypeAttr{"Name"}) { # register previous %{$TypeInfo{$LibVersion}{registerType($TypeAttr{"Name"}, $LibVersion)}} = %TypeAttr; } %TypeAttr = ("Type"=>$2, "Name"=>$3); # reset previous class $FieldPos = 0; # reset field position $CurrentMethod = ""; # reset current method $TypeAttr{"Archive"} = $ArchiveName; if($TypeAttr{"Name"}=~/\A(.+)\.([^.]+)\Z/) { $CurrentClass = $2; $TypeAttr{"Package"} = $1; $CurrentPackage = $TypeAttr{"Package"}; } else { $CurrentClass = $TypeAttr{"Name"}; $CurrentPackage = ""; } if($CurrentClass=~s/#/./g) { # javax.swing.text.GlyphView.GlyphPainter <=> GlyphView$GlyphPainter $TypeAttr{"Name"}=~s/#/./g; } if($LINE=~/(\A|\s+)(public|protected|private)\s+/) { $TypeAttr{"Access"} = $2; } else { $TypeAttr{"Access"} = "package-private"; } if($LINE=~/\s+extends\s+([^\s\{]+)/) { if($TypeAttr{"Type"} eq "class") { $TypeAttr{"SuperClass"} = registerType($1, $LibVersion); } elsif($TypeAttr{"Type"} eq "interface") { foreach my $SuperInterface (split(/,/, $1)) { $TypeAttr{"SuperInterface"}{registerType($SuperInterface, $LibVersion)} = 1; } } } if($LINE=~/\s+implements\s+([^\s\{]+)/) { foreach my $SuperInterface (split(/,/, $1)) { $TypeAttr{"SuperInterface"}{registerType($SuperInterface, $LibVersion)} = 1; } } if($LINE=~/(\A|\s+)abstract\s+/) { $TypeAttr{"Abstract"} = 1; } if($LINE=~/(\A|\s+)final\s+/) { $TypeAttr{"Final"} = 1; } if($LINE=~/(\A|\s+)static\s+/) { $TypeAttr{"Static"} = 1; } } elsif($CurrentMethod and index($LINE, "Deprecated: true")!=-1) { # deprecated method $MethodInfo{$LibVersion}{$CurrentMethod}{"Deprecated"} = 1; } elsif($CurrentClass and index($LINE, "Deprecated: length")!=-1) { # deprecated method $TypeAttr{"Deprecated"} = 1; } else { # unparsed } } if($TypeAttr{"Name"}) { # register last %{$TypeInfo{$LibVersion}{registerType($TypeAttr{"Name"}, $LibVersion)}} = %TypeAttr; } } sub registerUsage($$) { my ($TypeId, $LibVersion) = @_; $Class_Constructed{$LibVersion}{$TypeId} = 1; if(my $BaseId = $TypeInfo{$LibVersion}{$TypeId}{"BaseType"}) { $Class_Constructed{$LibVersion}{$BaseId} = 1; } } sub checkVoidMethod($) { my $Method = $_[0]; return "" if(not $Method); if($Method=~s/\)(.+)\Z/\)V/g) { return $Method; } else { return ""; } } sub detectAdded() { foreach my $Method (keys(%{$MethodInfo{2}})) { if(not defined $MethodInfo{1}{$Method}) { if($MethodInfo{2}{$Method}{"Access"}=~/private/) { # non-public methods next; } next if(not methodFilter($Method, 2)); my $ClassId = $MethodInfo{2}{$Method}{"Class"}; my %Class = get_Type($ClassId, 2); if($Class{"Access"}=~/private/) { # non-public classes next; } $CheckedMethods{$Method} = 1; if(not $MethodInfo{2}{$Method}{"Constructor"} and my $Overridden = findMethod($Method, 2, $Class{"Name"}, 2)) { if(defined $MethodInfo{1}{$Overridden} and get_TypeType($ClassId, 2) eq "class" and $TName_Tid{1}{$Class{"Name"}}) { # class should exist in previous version %{$CompatProblems{$Overridden}{"Class_Overridden_Method"}{"this.".get_SFormat($Method)}}=( "Type_Name"=>$Class{"Name"}, "Target"=>$MethodInfo{2}{$Method}{"Signature"}, "Old_Value"=>$MethodInfo{2}{$Overridden}{"Signature"}, "New_Value"=>$MethodInfo{2}{$Method}{"Signature"} ); } } if($MethodInfo{2}{$Method}{"Abstract"}) { $AddedMethod_Abstract{$Class{"Name"}}{$Method} = 1; } if(not $ShortMode) { %{$CompatProblems{$Method}{"Added_Method"}{""}}=(); } if(not $MethodInfo{2}{$Method}{"Constructor"}) { if(get_TypeName($MethodInfo{2}{$Method}{"Return"}, 2) ne "void" and my $VoidMethod = checkVoidMethod($Method)) { if(defined $MethodInfo{1}{$VoidMethod}) { # return value type changed from "void" to $ChangedReturnFromVoid{$VoidMethod} = 1; $ChangedReturnFromVoid{$Method} = 1; %{$CompatProblems{$VoidMethod}{"Changed_Method_Return_From_Void"}{""}}=( "New_Value"=>get_TypeName($MethodInfo{2}{$Method}{"Return"}, 2) ); } } } } } } sub detectRemoved() { foreach my $Method (keys(%{$MethodInfo{1}})) { if(not defined $MethodInfo{2}{$Method}) { next if($MethodInfo{1}{$Method}{"Access"}=~/private/); next if(not methodFilter($Method, 1)); my $ClassId = $MethodInfo{1}{$Method}{"Class"}; my %Class = get_Type($ClassId, 1); if($Class{"Access"}=~/private/) {# non-public classes next; } $CheckedMethods{$Method} = 1; if(not $MethodInfo{1}{$Method}{"Constructor"} and $TName_Tid{2}{$Class{"Name"}} and my $MovedUp = findMethod($Method, 1, $Class{"Name"}, 2)) { if(get_TypeType($ClassId, 1) eq "class" and not $MethodInfo{1}{$Method}{"Abstract"} and $TName_Tid{2}{$Class{"Name"}}) {# class should exist in newer version %{$CompatProblems{$Method}{"Class_Method_Moved_Up_Hierarchy"}{"this.".get_SFormat($MovedUp)}}=( "Type_Name"=>$Class{"Name"}, "Target"=>$MethodInfo{2}{$MovedUp}{"Signature"}, "Old_Value"=>$MethodInfo{1}{$Method}{"Signature"}, "New_Value"=>$MethodInfo{2}{$MovedUp}{"Signature"} ); } } else { if($MethodInfo{1}{$Method}{"Abstract"}) { $RemovedMethod_Abstract{$Class{"Name"}}{$Method} = 1; } %{$CompatProblems{$Method}{"Removed_Method"}{""}}=(); } } } } sub getArchives($) { my $LibVersion = $_[0]; my @Paths = (); foreach my $Path (split(/\s*\n\s*/, $Descriptor{$LibVersion}{"Archives"})) { if(not -e $Path) { exitStatus("Access_Error", "can't access \'$Path\'"); } foreach (getArchivePaths($Path, $LibVersion)) { push(@Paths, $_); } } return @Paths; } sub getArchivePaths($$) { my ($Dest, $LibVersion) = @_; if(-f $Dest) { return ($Dest); } elsif(-d $Dest) { $Dest=~s/[\/\\]+\Z//g; my @AllClasses = (); foreach my $Path (cmd_find($Dest,"","*\.jar","")) { next if(ignore_path($Path, $Dest)); push(@AllClasses, resolve_symlink($Path)); } return @AllClasses; } return (); } sub isCyclical($$) { return (grep {$_ eq $_[1]} @{$_[0]}); } sub read_symlink($) { my $Path = $_[0]; return "" if(not $Path or not -f $Path); return $Cache{"read_symlink"}{$Path} if(defined $Cache{"read_symlink"}{$Path}); if(my $ReadlinkCmd = get_CmdPath("readlink")) { my $Res = `$ReadlinkCmd -n \"$Path\"`; return ($Cache{"read_symlink"}{$Path} = $Res); } elsif(my $FileCmd = get_CmdPath("file")) { my $Info = `$FileCmd \"$Path\"`; if($Info=~/symbolic\s+link\s+to\s+['`"]*([\w\d\.\-\/\\]+)['`"]*/i) { return ($Cache{"read_symlink"}{$Path} = $1); } } return ($Cache{"read_symlink"}{$Path} = ""); } sub resolve_symlink($) { my $Path = $_[0]; return "" if(not $Path or not -f $Path); return $Path if(isCyclical(\@RecurSymlink, $Path)); push(@RecurSymlink, $Path); if(-l $Path and my $Redirect=read_symlink($Path)) { if(is_abs($Redirect)) { my $Res = resolve_symlink($Redirect); pop(@RecurSymlink); return $Res; } elsif($Redirect=~/\.\.[\/\\]/) { $Redirect = joinPath(get_dirname($Path),$Redirect); while($Redirect=~s&(/|\\)[^\/\\]+(\/|\\)\.\.(\/|\\)&$1&){}; my $Res = resolve_symlink($Redirect); pop(@RecurSymlink); return $Res; } elsif(-f get_dirname($Path)."/".$Redirect) { my $Res = resolve_symlink(joinPath(get_dirname($Path),$Redirect)); pop(@RecurSymlink); return $Res; } return $Path; } else { pop(@RecurSymlink); return $Path; } } sub cmpVersions($$) {# compare two version strings in dotted-numeric format my ($V1, $V2) = @_; return 0 if($V1 eq $V2); my @V1Parts = split(/\./, $V1); my @V2Parts = split(/\./, $V2); for (my $i = 0; $i <= $#V1Parts && $i <= $#V2Parts; $i++) { return -1 if(int($V1Parts[$i]) < int($V2Parts[$i])); return 1 if(int($V1Parts[$i]) > int($V2Parts[$i])); } return -1 if($#V1Parts < $#V2Parts); return 1 if($#V1Parts > $#V2Parts); return 0; } sub majorVersion($) { my $Version = $_[0]; return 0 if(not $Version); my @VParts = split(/\./, $Version); return $VParts[0]; } sub isDump($) { if(get_filename($_[0])=~/\A(.+)\.api(\Q.tar.gz\E|\Q.zip\E|)\Z/) { # returns a name of package return $1; } return 0; } sub read_API_Dump($$) { my ($LibVersion, $Path) = @_; return if(not $LibVersion or not -e $Path); my $FileName = unpackDump($Path); if($FileName!~/\.api\Z/) { exitStatus("Invalid_Dump", "specified API dump \'$Path\' is not valid, try to recreate it"); } my $Content = readFile($FileName); unlink($FileName); if($Content!~/};\s*\Z/) { exitStatus("Invalid_Dump", "specified API dump \'$Path\' is not valid, try to recreate it"); } my $LibraryAPI = eval($Content); if(not $LibraryAPI) { exitStatus("Error", "internal error - eval() procedure seem to not working correctly, try to remove 'use strict' and try again"); } my $DumpVersion = $LibraryAPI->{"API_DUMP_VERSION"}; if(majorVersion($DumpVersion) ne $API_DUMP_MAJOR) { # compatible with the dumps of the same major version exitStatus("Dump_Version", "incompatible version $DumpVersion of specified API dump (allowed only $API_DUMP_MAJOR.0<=V<=$API_DUMP_MAJOR.9)"); } $TypeInfo{$LibVersion} = $LibraryAPI->{"TypeInfo"}; foreach my $TypeId (keys(%{$TypeInfo{$LibVersion}})) { my %TypeAttr = %{$TypeInfo{$LibVersion}{$TypeId}}; $TName_Tid{$LibVersion}{$TypeAttr{"Name"}}=$TypeId; if(my $Archive = $TypeAttr{"Archive"}) { $LibArchives{$LibVersion}{$Archive}=1; } foreach my $FieldName (keys(%{$TypeAttr{"Fields"}})) { if($TypeAttr{"Fields"}{$FieldName}{"Access"}=~/public|protected/) { $Class_Fields{$LibVersion}{$TypeAttr{"Name"}}{$FieldName}=$TypeAttr{"Fields"}{$FieldName}{"Type"}; } } } $MethodInfo{$LibVersion} = $LibraryAPI->{"MethodInfo"}; foreach my $Method (keys(%{$MethodInfo{$LibVersion}})) { if(my $ClassId = $MethodInfo{$LibVersion}{$Method}{"Class"} and $MethodInfo{$LibVersion}{$Method}{"Access"}=~/public|protected/) { $Class_Methods{$LibVersion}{get_TypeName($ClassId, $LibVersion)}{$Method}=1; if($MethodInfo{$LibVersion}{$Method}{"Abstract"}) { $Class_AbstractMethods{$LibVersion}{get_TypeName($ClassId, $LibVersion)}{$Method}=1; } $LibClasses{$LibVersion}{get_ShortName($ClassId, $LibVersion)}=$MethodInfo{$LibVersion}{$Method}{"Package"}; } } if(keys(%{$LibArchives{$LibVersion}})) { $Descriptor{$LibVersion}{"Archives"}="OK"; } $Descriptor{$LibVersion}{"Version"} = $LibraryAPI->{"Version"}; $Descriptor{$LibVersion}{"Dump"}=1; } sub createDescriptor($$) { my ($LibVersion, $Path) = @_; return if(not $LibVersion or not $Path or not -e $Path); if(isDump($Path)) { # API dump read_API_Dump($LibVersion, $Path); } else { if(-d $Path or $Path=~/\.jar\Z/) { readDescriptor($LibVersion," ".$TargetVersion{$LibVersion}." $Path "); } else { # standard XML descriptor readDescriptor($LibVersion, readFile($Path)); } } } sub get_version($) { my $Cmd = $_[0]; return "" if(not $Cmd); my $Version = `$Cmd --version 2>\"$TMP_DIR/null\"`; return $Version; } sub get_depth($) { if(defined $Cache{"get_depth"}{$_[0]}) { return $Cache{"get_depth"}{$_[0]} } return ($Cache{"get_depth"}{$_[0]} = ($_[0]=~tr![\/\\]|\:\:!!)); } sub show_time_interval($) { my $Interval = $_[0]; my $Hr = int($Interval/3600); my $Min = int($Interval/60)-$Hr*60; my $Sec = $Interval-$Hr*3600-$Min*60; if($Hr) { return "$Hr hr, $Min min, $Sec sec"; } elsif($Min) { return "$Min min, $Sec sec"; } else { return "$Sec sec"; } } sub checkVersionNum($$) { my ($LibVersion, $Path) = @_; if(my $VerNum = $TargetVersion{$LibVersion}) { return $VerNum; } my $Alt = 0; my $VerNum = ""; foreach my $Part (split(/\s*,\s*/, $Path)) { if(not $VerNum and -d $Part) { $Alt = 1; $Part=~s/\Q$TargetLibraryName\E//g; $VerNum = parseVersion($Part); } if(not $VerNum and $Part=~/\.jar\Z/i) { $Alt = 1; $VerNum = readJarVersion(get_abs_path($Part)); if(not $VerNum) { $VerNum = getPkgVersion(get_filename($Part)); } if(not $VerNum) { $VerNum = parseVersion($Part); } } if($VerNum) { $TargetVersion{$LibVersion} = $VerNum; printMsg("WARNING", "set ".($LibVersion==1?"1st":"2nd")." version number to $VerNum (use -v$LibVersion option to change it)"); return $TargetVersion{$LibVersion}; } } if($Alt) { if($DumpAPI) { exitStatus("Error", "version number is not set (use -vnum option)"); } else { exitStatus("Error", ($LibVersion==1?"1st":"2nd")." version number is not set (use -v$LibVersion option)"); } } } sub readJarVersion($) { my $Path = $_[0]; return "" if(not $Path or not -e $Path); my $JarCmd = get_CmdPath("jar"); if(not $JarCmd) { exitStatus("Not_Found", "can't find \"jar\" command"); } chdir($TMP_DIR); system($JarCmd." -xf \"$Path\" META-INF 2>null"); chdir($ORIG_DIR); if(my $Content = readFile("$TMP_DIR/META-INF/MANIFEST.MF")) { if($Content=~/(\A|\s)Implementation\-Version:\s*(.+)(\s|\Z)/i) { return $2; } } return ""; } sub parseVersion($) { my $Str = $_[0]; return "" if(not $Str); if($Str=~/(\/|\\|\w|\A)[\-\_]*(\d+[\d\.\-]+\d+|\d+)/) { return $2; } return ""; } sub getPkgVersion($) { my $Name = $_[0]; $Name=~s/\.\w+\Z//; if($Name=~/\A(.+[a-z])[\-\_](v|ver|)(\d.+?)\Z/i) { # libsample-N # libsample-vN return ($1, $3); } elsif($Name=~/\A(.+?)(\d[\d\.]*)\Z/i) { # libsampleN return ($1, $2); } elsif($Name=~/\A(.+)[\-\_](v|ver|)(.+?)\Z/i) { # libsample-N # libsample-vN return ($1, $3); } elsif($Name=~/\A([a-z_\-]+)(\d.+?)\Z/i) { # libsampleNb return ($1, $2); } return (); } sub get_OSgroup() { if($Config{"osname"}=~/macos|darwin|rhapsody/i) { return "macos"; } elsif($Config{"osname"}=~/freebsd|openbsd|netbsd/i) { return "bsd"; } elsif($Config{"osname"}=~/haiku|beos/i) { return "beos"; } elsif($Config{"osname"}=~/symbian|epoc/i) { return "symbian"; } elsif($Config{"osname"}=~/win/i) { return "windows"; } else { return $Config{"osname"}; } } sub get_ARG_MAX() { if($OSgroup eq "windows") { return 1990; # 8191, 2047 } else { # Linux # TODO: set max possible value (~131000) return 32767; } } sub dump_sorting($) { my $Hash = $_[0]; return [] if(not $Hash); my @Keys = keys(%{$Hash}); return [] if($#Keys<0); if($Keys[0]=~/\A\d+\Z/) { # numbers return [sort {int($a)<=>int($b)} @Keys]; } else { # strings return [sort {$a cmp $b} @Keys]; } } sub detect_bin_default_paths() { my $EnvPaths = $ENV{"PATH"}; if($OSgroup eq "beos") { $EnvPaths.=":".$ENV{"BETOOLS"}; } elsif($OSgroup eq "windows" and my $JHome = $ENV{"JAVA_HOME"}) { $EnvPaths.=";$JHome\\bin"; } my $Sep = ($OSgroup eq "windows")?";":":|;"; foreach my $Path (sort {length($a)<=>length($b)} split(/$Sep/, $EnvPaths)) { $Path=~s/[\/\\]+\Z//g; next if(not $Path); $DefaultBinPaths{$Path} = 1; } } sub detect_default_paths() { foreach my $Type (keys(%{$OS_AddPath{$OSgroup}})) {# additional search paths foreach my $Path (keys(%{$OS_AddPath{$OSgroup}{$Type}})) { next if(not -d $Path); $SystemPaths{$Type}{$Path} = $OS_AddPath{$OSgroup}{$Type}{$Path}; } } if($OSgroup ne "windows") { foreach my $Type ("include", "lib", "bin") {# autodetecting system "devel" directories foreach my $Path (cmd_find("/","d","*$Type*",1)) { $SystemPaths{$Type}{$Path} = 1; } if(-d "/usr") { foreach my $Path (cmd_find("/usr","d","*$Type*",1)) { $SystemPaths{$Type}{$Path} = 1; } } } } detect_bin_default_paths(); foreach my $Path (keys(%DefaultBinPaths)) { $SystemPaths{"bin"}{$Path} = $DefaultBinPaths{$Path}; } if(my $JavaCmd = get_CmdPath("java")) { if(my $Ver = `$JavaCmd -version 2>&1`) { if($Ver=~/java version "(.+)\"/) { printMsg("INFO", "Using Java $1"); } } } } sub exitStatus($$) { my ($Code, $Msg) = @_; print STDERR "ERROR: ". $Msg."\n"; exit($ERROR_CODE{$Code}); } sub printMsg($$) { my ($Type, $Msg) = @_; if($Type!~/\AINFO/) { $Msg = $Type.": ".$Msg; } if($Type!~/_C\Z/) { $Msg .= "\n"; } if($Type eq "ERROR") { print STDERR $Msg; } else { print $Msg; } } sub printStatMsg($) { my $Level = $_[0]; printMsg("INFO", "total \"$Level\" compatibility problems: ".$RESULT{$Level}{"Problems"}.", warnings: ".$RESULT{$Level}{"Warnings"}); } sub printReport() { printMsg("INFO", "creating compatibility report ..."); createReport(); if($JoinReport or $DoubleReport) { if($RESULT{"Binary"}{"Problems"} or $RESULT{"Source"}{"Problems"}) { printMsg("INFO", "result: INCOMPATIBLE (Binary: ".$RESULT{"Binary"}{"Affected"}."\%, Source: ".$RESULT{"Source"}{"Affected"}."\%)"); } else { printMsg("INFO", "result: COMPATIBLE"); } printStatMsg("Binary"); printStatMsg("Source"); } elsif($BinaryOnly) { if($RESULT{"Binary"}{"Problems"}) { printMsg("INFO", "result: INCOMPATIBLE (".$RESULT{"Binary"}{"Affected"}."\%)"); } else { printMsg("INFO", "result: COMPATIBLE"); } printStatMsg("Binary"); } elsif($SourceOnly) { if($RESULT{"Source"}{"Problems"}) { printMsg("INFO", "result: INCOMPATIBLE (".$RESULT{"Source"}{"Affected"}."\%)"); } else { printMsg("INFO", "result: COMPATIBLE"); } printStatMsg("Source"); } if($JoinReport) { printMsg("INFO", "see detailed report:\n ".getReportPath("Join")); } elsif($DoubleReport) { # default printMsg("INFO", "see detailed reports:\n ".getReportPath("Binary")."\n ".getReportPath("Source")); } elsif($BinaryOnly) { # --binary printMsg("INFO", "see detailed report:\n ".getReportPath("Binary")); } elsif($SourceOnly) { # --source printMsg("INFO", "see detailed report:\n ".getReportPath("Source")); } } sub getReportPath($) { my $Level = $_[0]; my $Dir = "compat_reports/$TargetLibraryName/".$Descriptor{1}{"Version"}."_to_".$Descriptor{2}{"Version"}; if($Level eq "Binary") { if($BinaryReportPath) { # --bin-report-path return $BinaryReportPath; } elsif($OutputReportPath) { # --report-path return $OutputReportPath; } else { # default return $Dir."/bin_compat_report.html"; } } elsif($Level eq "Source") { if($SourceReportPath) { # --src-report-path return $SourceReportPath; } elsif($OutputReportPath) { # --report-path return $OutputReportPath; } else { # default return $Dir."/src_compat_report.html"; } } else { if($OutputReportPath) { # --report-path return $OutputReportPath; } else { # default return $Dir."/compat_report.html"; } } } sub initLogging($) { my $LibVersion = $_[0]; if($Debug) { # debug directory $DEBUG_PATH{$LibVersion} = "debug/$TargetLibraryName/".$Descriptor{$LibVersion}{"Version"}; rmtree($DEBUG_PATH{$LibVersion}); } } sub createArchive($$) { my ($Path, $To) = @_; if(not $Path or not -e $Path or not -d $To) { return ""; } my ($From, $Name) = separate_path($Path); if($OSgroup eq "windows") { # *.zip my $ZipCmd = get_CmdPath("zip"); if(not $ZipCmd) { exitStatus("Not_Found", "can't find \"zip\""); } my $Pkg = $To."/".$Name.".zip"; unlink($Pkg); chdir($To); system("$ZipCmd -j \"$Name.zip\" \"$Path\" >\"$TMP_DIR/null\""); if($?) { # cannot allocate memory (or other problems with "zip") unlink($Path); exitStatus("Error", "can't pack the API dump: ".$!); } chdir($ORIG_DIR); unlink($Path); return $Pkg; } else { # *.tar.gz my $TarCmd = get_CmdPath("tar"); if(not $TarCmd) { exitStatus("Not_Found", "can't find \"tar\""); } my $GzipCmd = get_CmdPath("gzip"); if(not $GzipCmd) { exitStatus("Not_Found", "can't find \"gzip\""); } my $Pkg = abs_path($To)."/".$Name.".tar.gz"; unlink($Pkg); chdir($From); system($TarCmd, "-czf", $Pkg, $Name); if($?) { # cannot allocate memory (or other problems with "tar") unlink($Path); exitStatus("Error", "can't pack the API dump: ".$!); } chdir($ORIG_DIR); unlink($Path); return $To."/".$Name.".tar.gz"; } } sub scenario() { if($BinaryOnly and $SourceOnly) { # both --binary and --source # is the default mode $DoubleReport = 1; $JoinReport = 0; $BinaryOnly = 0; $SourceOnly = 0; if($OutputReportPath) { # --report-path $DoubleReport = 0; $JoinReport = 1; } } elsif($BinaryOnly or $SourceOnly) { # --binary or --source $DoubleReport = 0; $JoinReport = 0; } if(defined $Help) { HELP_MESSAGE(); exit(0); } if(defined $ShowVersion) { printMsg("INFO", "Java API Compliance Checker (Java ACC) $TOOL_VERSION\nCopyright (C) 2013 ROSA Lab\nLicense: LGPL or GPL \nThis program is free software: you can redistribute it and/or modify it.\n\nWritten by Andrey Ponomarenko."); exit(0); } if(defined $DumpVersion) { printMsg("INFO", $TOOL_VERSION); exit(0); } $Data::Dumper::Sortkeys = 1; # FIXME: can't pass \&dump_sorting - cause a segfault sometimes if($SortDump) { $Data::Dumper::Useperl = 1; $Data::Dumper::Sortkeys = \&dump_sorting; } if(defined $TestSystem) { detect_default_paths(); testSystem(); exit(0); } if($GenerateDescriptor) { writeFile("VERSION.xml", $Descriptor_Template."\n"); printMsg("INFO", "descriptor template VERSION.xml has been generated into the current directory"); exit(0); } if(not $TargetLibraryName) { if($DumpAPI) { if($DumpAPI=~/\.jar\Z/) { # short usage my ($Name, $Version) = getPkgVersion(get_filename($DumpAPI)); if($Name and $Version ne "") { $TargetLibraryName = $Name; if(not $TargetVersion{1}) { $TargetVersion{1} = $Version; } } } } else { if($Descriptor{1}{"Path"}=~/\.jar\Z/ and $Descriptor{1}{"Path"}=~/\.jar\Z/) { # short usage my ($Name1, $Version1) = getPkgVersion(get_filename($Descriptor{1}{"Path"})); my ($Name2, $Version2) = getPkgVersion(get_filename($Descriptor{2}{"Path"})); if($Name1 and $Version1 ne "" and $Version2 ne "") { $TargetLibraryName = $Name1; if(not $TargetVersion{1}) { $TargetVersion{1} = $Version1; } if(not $TargetVersion{2}) { $TargetVersion{2} = $Version2; } } } } if(not $TargetLibraryName) { exitStatus("Error", "library name is not selected (option -l)"); } } else { # validate library name if($TargetLibraryName=~/[\*\/\\]/) { exitStatus("Error", "\"\\\", \"\/\" and \"*\" symbols are not allowed in the library name"); } } if(not $TargetLibraryFullName) { $TargetLibraryFullName = $TargetLibraryName; } if($ClassListPath) { if(not -f $ClassListPath) { exitStatus("Access_Error", "can't access file \'$ClassListPath\'"); } foreach my $Class (split(/\n/, readFile($ClassListPath))) { $Class=~s/\//./g; $ClassList_User{$Class} = 1; } } if($SkipClasses) { if(not -f $SkipClasses) { exitStatus("Access_Error", "can't access file \'$SkipClasses\'"); } foreach my $Class (split(/\n/, readFile($SkipClasses))) { $Class=~s/\//./g; $SkipClass_User{$Class} = 1; } } if($ClientPath) { if(-f $ClientPath) { readUsage_Client($ClientPath) } else { exitStatus("Access_Error", "can't access file \'$ClientPath\'"); } } if($DumpAPI) { foreach my $Part (split(/\s*,\s*/, $DumpAPI)) { if(not -e $Part) { exitStatus("Access_Error", "can't access \'$Part\'"); } } detect_default_paths(); checkVersionNum(1, $DumpAPI); my $TarCmd = get_CmdPath("tar"); if(not $TarCmd) { exitStatus("Not_Found", "can't find \"tar\""); } my $GzipCmd = get_CmdPath("gzip"); if(not $GzipCmd) { exitStatus("Not_Found", "can't find \"gzip\""); } foreach my $Part (split(/\s*,\s*/, $DumpAPI)) { createDescriptor(1, $Part); } if(not $Descriptor{1}{"Archives"}) { exitStatus("Error", "descriptor does not contain Java ARchives"); } initLogging(1); readArchives(1); my %LibraryAPI = (); printMsg("INFO", "creating library API dump ..."); $LibraryAPI{"MethodInfo"} = $MethodInfo{1}; $LibraryAPI{"TypeInfo"} = $TypeInfo{1}; $LibraryAPI{"Version"} = $Descriptor{1}{"Version"}; $LibraryAPI{"Library"} = $TargetLibraryName; $LibraryAPI{"API_DUMP_VERSION"} = $API_DUMP_VERSION; $LibraryAPI{"TOOL_VERSION"} = $TOOL_VERSION; my $DumpPath = "api_dumps/$TargetLibraryName/".$TargetLibraryName."_".$Descriptor{1}{"Version"}.".api.".$AR_EXT; if(not $DumpPath=~s/\Q.$AR_EXT\E\Z//g) { exitStatus("Error", "the dump path (-dump-path option) should be a path to a *.$AR_EXT file"); } my ($DDir, $DName) = separate_path($DumpPath); my $DPath = $TMP_DIR."/".$DName; mkpath($DDir); writeFile($DPath, Dumper(\%LibraryAPI)); if(not -s $DPath) { exitStatus("Error", "can't create API dump because something is going wrong with the Data::Dumper module"); } my $Pkg = createArchive($DPath, $DDir); printMsg("INFO", "library API has been dumped to:\n $Pkg"); printMsg("INFO", "you can transfer this dump everywhere and use instead of the ".$Descriptor{1}{"Version"}." version descriptor"); exit(0); } if(not $Descriptor{1}{"Path"}) { exitStatus("Error", "-old option is not specified"); } foreach my $Part (split(/\s*,\s*/, $Descriptor{1}{"Path"})) { if(not -e $Part) { exitStatus("Access_Error", "can't access \'$Part\'"); } } if(not $Descriptor{2}{"Path"}) { exitStatus("Error", "-new option is not specified"); } foreach my $Part (split(/\s*,\s*/, $Descriptor{2}{"Path"})) { if(not -e $Part) { exitStatus("Access_Error", "can't access \'$Part\'"); } } detect_default_paths(); checkVersionNum(1, $Descriptor{1}{"Path"}); checkVersionNum(2, $Descriptor{2}{"Path"}); foreach my $Part (split(/\s*,\s*/, $Descriptor{1}{"Path"})) { createDescriptor(1, $Part); } foreach my $Part (split(/\s*,\s*/, $Descriptor{2}{"Path"})) { createDescriptor(2, $Part); } if(not $Descriptor{1}{"Archives"}) { exitStatus("Error", "descriptor d1 does not contain Java ARchives"); } if(not $Descriptor{2}{"Archives"}) { exitStatus("Error", "descriptor d2 does not contain Java ARchives"); } initLogging(1); initLogging(2); if($Descriptor{1}{"Archives"} and not $Descriptor{1}{"Dump"}) { readArchives(1); } if($Descriptor{2}{"Archives"} and not $Descriptor{2}{"Dump"}) { readArchives(2); } foreach my $ClassName (keys(%ClassMethod_AddedInvoked)) { foreach my $MethodName (keys(%{$ClassMethod_AddedInvoked{$ClassName}})) { if(defined $MethodInfo{1}{$MethodName} or defined $MethodInfo{2}{$MethodName} or defined $MethodInvoked{1}{$MethodName} or findMethod($MethodName, 2, $ClassName, 1)) { # abstract method added by the new super-class (abstract) or super-interface delete($ClassMethod_AddedInvoked{$ClassName}{$MethodName}); } } if(not keys(%{$ClassMethod_AddedInvoked{$ClassName}})) { delete($ClassMethod_AddedInvoked{$ClassName}); } } prepareMethods(1); prepareMethods(2); detectAdded(); detectRemoved(); printMsg("INFO", "comparing classes ..."); mergeClasses(); mergeMethods(); if($CheckImpl) { mergeImplementations(); } printReport(); if($RESULT{"Source"}{"Problems"} + $RESULT{"Binary"}{"Problems"}) { exit($ERROR_CODE{"Incompatible"}); } else { exit($ERROR_CODE{"Compatible"}); } } scenario();